الحصول على حجم رأس مختلف عن طريق تغيير حجم النافذة

StackOverflow https://stackoverflow.com/questions/149995

  •  02-07-2019
  •  | 
  •  

سؤال

لدي برنامج C++ يمثل رأس TCP كبنية:

#include "stdafx.h"

/*  TCP HEADER

    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

*/

typedef struct {        // RFC793
    WORD         wSourcePort;
    WORD         wDestPort;
    DWORD        dwSequence;
    DWORD        dwAcknowledgment;
    unsigned int byReserved1:4;
    unsigned int byDataOffset:4;
    unsigned int fFIN:1;
    unsigned int fSYN:1;
    unsigned int fRST:1;
    unsigned int fPSH:1;
    unsigned int fACK:1;
    unsigned int fURG:1;
    unsigned int byReserved2:2;
    unsigned short wWindow;
    WORD         wChecksum;
    WORD         wUrgentPointer;
} TCP_HEADER, *PTCP_HEADER;


int _tmain(int argc, _TCHAR* argv[])
{
    printf("TCP header length: %d\n", sizeof(TCP_HEADER));
    return 0;
}

إذا قمت بتشغيل هذا البرنامج أحصل على حجم هذا الرأس وهو 24 بايت، وهو ليس الحجم الذي كنت أتوقعه.إذا قمت بتغيير نوع الحقل "wWindow" إلى "unsigned int wWindow:16"، والذي يحتوي على نفس عدد البتات مثل الحقل القصير غير الموقع، يخبرني البرنامج أن حجم البنية أصبح الآن 20 بايت، وهو الحجم الصحيح.لماذا هذا؟

أنا أستخدم Microsoft Visual Studio 2005 مع SP1 على جهاز 32 بت x86.

هل كانت مفيدة؟

المحلول

انظر هذا السؤال: لماذا لا يساوي sizeof للبنية مجموع حجم كل عضو؟ .

أعتقد أن المترجم يأخذ تلميحًا لتعطيل الحشو عند استخدام بناء الجملة "unsigned int wWindow:16".

لاحظ أيضًا أن الاختصار ليس مضمونًا أن يكون 16 بت.الضمان هو أن:16 بت <= حجم قصير <= حجم int.

نصائح أخرى

لأن المترجم يقوم بتعبئة حقل البت الخاص بك في كيان 32 بت، وليس كيان 16 بت.

بشكل عام، يجب عليك تجنب حقول البتات واستخدام ثوابت البيان الأخرى (التعدادات أو أي شيء آخر) مع إخفاء البتات الواضحة والتحويل للوصول إلى "الحقول الفرعية" في الحقل.

إليك أحد الأسباب التي تدعو إلى تجنب حقول البت - فهي ليست قابلة للتنقل بين المترجمين حتى لنفس النظام الأساسي.من معيار C99 (توجد صياغة مماثلة في معيار C90):

يجوز للتنفيذ تخصيص أي وحدة تخزين قابلة للعنونة كبيرة بما يكفي لعقد Bitfield.إذا بقيت مساحة كافية ، يجب تعبئة حقل صغير يتبع حقلًا بت آخر في هيكل فورًا في أجزاء مجاورة من نفس الوحدة.إذا بقيت المساحة غير الكافية ، سواء تم وضع حقل بت من الملاءمة في الوحدة التالية أو التداخل المتداخل ، فسيتم تعريف التنفيذ.يتم تعريف ترتيب تخصيص حقول البتات داخل وحدة (عالية الترتيب إلى الترتيب المنخفض أو المنخفض إلى الترتيب العالي).محاذاة وحدة التخزين القابلة للعنونة غير محددة.

لا يمكنك ضمان ما إذا كان حقل البت "سيمتد" إلى حدود int أم لا، ولا يمكنك تحديد ما إذا كان حقل البت يبدأ عند النهاية المنخفضة لـ int أو النهاية العليا لـ int (وهذا يعتمد على ما إذا كان المعالج إنديان كبير أو إنديان صغير).

تستخدم سلسلتك من حقول البت "int:xx" غير الموقعة ما يصل إلى 16 بت فقط من أصل 32 بت في int.أما الـ 16 بت الأخرى (2 بايت) فهي موجودة، ولكنها غير مستخدمة.ويتبع ذلك الكلمة القصيرة غير الموقعة، والتي تقع على حد int، ثم WORD، والتي تتم محاذاتها على حد int مما يعني أن هناك بايتتين من الحشو بينهما.

عند التبديل إلى "unsigned int wWindow:16"، بدلاً من أن يكون قصيرًا منفصلاً، يستخدم المترجم الأجزاء غير المستخدمة من حقل البت السابق، لذلك لا هدر، ولا قصير، ولا توجد حشوة بعد الاختصار، وبالتالي يمكنك حفظ أربعة بايت.

يقوم المترجم بحشو عضو البنية الذي لا يحتوي على حقول البت بمحاذاة الكلمات الأصلية ذات 32 بت.لإصلاح ذلك، قم بتنفيذ #pragma pack(0) قبل البنية و#pragma pack() بعد ذلك.

يمكن للمترجم أن يحشو حدود البنية في الذاكرة اعتمادًا على حجم الحقول وترتيبها.

لست خبيرًا في C/C++ عندما يتعلق الأمر بالتعبئة.لكنني أتخيل أن هناك قاعدة في المواصفات تنص على أنه عندما يتبع حقل غير بت حقلًا، يجب محاذاته على حدود الكلمة بغض النظر عما إذا كان مناسبًا للمساحة المتبقية أم لا.من خلال جعله ناقلًا نقطيًا صريحًا فإنك تتجنب هذه المشكلة.

مرة أخرى، هذه تكهنات مع لمسة من الخبرة.

مثير للاهتمام - أعتقد أن كلمة "WORD" سيتم تقييمها على أنها "اختصار غير موقع"، لذا ستواجه هذه المشكلة في أكثر من مكان.

انتبه أيضًا إلى أنك ستحتاج إلى التعامل مع مشكلات endian بأي قيمة تزيد عن 8 بتات.

أعتقد أن مايك بي فهم الأمر بشكل صحيح، ولكن ليس واضحًا تمامًا.عندما تسأل عن "قصير"، يتم محاذاته على حدود 32 بت.عندما تسأل عن int:16، فهو ليس كذلك.لذا فإن int:16 يناسب مباشرة بعد حقول ebit، بينما يتخطى Short 2 بايت ويبدأ عند الكتلة التالية ذات 32 بت.

بقية ما يقوله قابل للتطبيق تمامًا - لا ينبغي أبدًا استخدام حقل البت لترميز بنية مرئية خارجيًا، لأنه لا يوجد ضمان بشأن كيفية تخصيصها.وفي أحسن الأحوال، فإنها تنتمي إلى البرامج المضمنة حيث يعد حفظ البايت أمرًا مهمًا.وحتى هناك، لا يمكنك استخدامها للتحكم فعليًا في البتات في منافذ الذاكرة المعينة.

أنت ترى قيمًا مختلفة بسبب قواعد تعبئة المترجم.يمكنك رؤية القواعد الخاصة بالاستوديو المرئي هنا.

عندما يكون لديك بنية يجب تعبئتها (أو الالتزام ببعض متطلبات المحاذاة المحددة)، يجب عليك استخدام خيار #pragma pack().بالنسبة للتعليمات البرمجية الخاصة بك، يمكنك استخدام #pragma pack(0) الذي سيقوم بمحاذاة جميع أعضاء البنية على حدود البايت.يمكنك بعد ذلك استخدام #pragma pack() لإعادة ضبط تعبئة البنية إلى حالتها الافتراضية.يمكنك رؤية المزيد من المعلومات حول حزمة pragma هنا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top