Getting different header size by changing window size
我有一个C++程序,它表示一个TCP报头作为一个结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | #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 ", sizeof(TCP_HEADER)); return 0; } |
如果我运行这个程序,我会得到这个头的大小为24字节,这不是我所期望的大小。如果我将字段"wwindow"的类型更改为"unsigned int wwindow:16",它的位数与无符号短字符串的位数相同,那么程序会告诉我结构的大小现在是20字节,大小正确。为什么会这样?
我正在32位x86计算机上使用Microsoft Visual Studio 2005 SP1。
因为编译器将位字段打包成32位int,而不是16位实体。
一般来说,您应该避免使用位域,并使用其他具有显式位屏蔽和移位的清单常量(枚举或其他常量)来访问字段中的"子字段"。
这里有一个应该避免使用位域的原因——即使在同一个平台上,它们在编译器之间也不是非常可移植的。根据C99标准(C90标准中有类似的措辞):
An implementation may allocate any
addressable storage unit large enough
to hold a bitfield. If enough space
remains, a bit-field that immediately
follows another bit-field in a
structure shall be packed into
adjacent bits of the same unit. If
insufficient space remains, whether a
bit-field that does not fit is put
into the next unit or overlaps
adjacent units is
implementation-defined. The order of
allocation of bit-fields within a unit
(high-order to low-order or low-order
to high-order) is
implementation-defined. The alignment
of the addressable storage unit is
unspecified.
您不能保证一个位域是否"跨越"一个int边界,也不能指定一个位域是从int的低端还是从int的高端开始(这与处理器是big endian还是little endian无关)。
您的一系列"unsigned int:xx"位字段在一个int中只使用了32位中的16位。其他16位(2字节)在那里,但没有使用。后面跟一个无符号短字符,它在int边界上,然后是一个单词,它沿着int边界对齐,这意味着它们之间有2个字节的填充。
当您切换到"unsigned int wwindow:16"时,编译器使用前一个位字段的未使用部分,而不是单独的短字段,因此不会浪费、不短和短字段后没有填充,因此可以节省四个字节。
请看这个问题:为什么结构的sizeof不等于每个成员的sizeof之和?.
我相信编译器在使用"unsigned int wwindow:16"语法时会提示禁用填充。
另外,请注意,不能保证短路为16位。保证:16位<=短的大小<=整数的大小。
我想迈克B说得对,但不完全清楚。当你要求"短"时,它在32位边界上对齐。当你问int:16时,它不是。所以int:16正好位于息税前利润字段之后,而short跳过2个字节并从下一个32位块开始。
他所说的其余部分是完全适用的——位字段决不能用于编码外部可见的结构,因为对于如何分配它们没有保证。充其量,它们属于嵌入式程序,其中保存一个字节很重要。即使在那里,您也不能使用它们来实际控制内存映射端口中的位。
由于编译器打包规则,您看到的值不同。您可以在此处看到特定于Visual Studio的规则。
当您有一个必须打包的结构(或遵循某些特定的对齐要求)时,您应该使用pragma pack()选项。对于代码,可以使用pragma pack(0),它将在字节边界上对齐所有结构成员。然后可以使用pragma pack()将结构打包重置为其默认状态。您可以在这里看到关于pack pragma的更多信息。
有趣的是-我认为"单词"会被评估为"无符号空头",所以你会在多个地方遇到这个问题。
还要注意,您需要处理8位以上的任何值的endian问题。
在包装方面,不是一个C/C++专家。但是我认为规范中有一条规则,当一个非位字段跟随一个位字段时,它必须在单词边界上对齐,不管它是否适合剩余的空间。通过使其成为显式的位向量,您可以避免这个问题。
这也是一个有经验的推测。
编译器可以根据字段的大小和顺序填充内存中的结构边界。
编译器正在将非位域结构成员填充为32位--本机字对齐。要解决此问题,请在结构之前执行pragma pack(0),在结构之后执行pragma pack()。