通过更改窗口大小获得不同的标题大小
我有一个将TCP标头表示为结构的C ++程序:
#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",其位数与无符号short相同,则程序会告诉我该结构的大小现在为20个字节,即正确的大小。为什么是这样?
我在32位x86计算机上使用带有SP1的Microsoft Visual Studio 2005.
解决方案
因为编译器将位域打包为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的高端开始(这与处理器是否为整数无关)。大端或者小端)。
编译器会将非位域结构成员填充为32位本机字对齐。要解决此问题,请在struct之前执行#pragma pack(0),在其之后执行#pragma pack()。
编译器可以根据字段的大小和顺序来填充内存中的结构边界。
在打包方面不是C / C ++专家。但是我想象规范中有一条规则说,当一个非位域跟随一个位域时,无论它是否适合剩余空间,它都必须在字边界上对齐。通过使其成为显式位向量,可以避免此问题。
同样,这是一种带有经验的猜测。
看到以下问题:为什么结构的sizeof不等于每个成员的sizeof之和? 。
我相信,当我们使用" unsigned int wWindow:16"语法时,编译器会提示禁用填充。
另外,请注意,不能保证short为16位。保证是:16位<=短的大小<=整数的大小。
有趣的是,我认为" WORD"的取值为" unsigned short",因此我们可能会在多个地方遇到这个问题。
另外请注意,我们需要以8位以上的任何值处理字节序问题。
由于编译器打包规则,我们会看到不同的值。我们可以在此处查看特定于Visual Studio的规则。
当我们具有必须打包的结构(或者遵守某些特定的对齐要求)时,应使用#pragma pack()选项。对于代码,可以使用#pragma pack(0),它将在字节边界上对齐所有结构成员。然后,我们可以使用#pragma pack()将结构打包重置为默认状态。我们可以在此处查看有关打包实用程序的更多信息。
"无符号int:xx"位域系列仅使用int的32位中的16位。其他16位(2个字节)在那里,但未使用。然后是int边界上的无符号short,然后是int边界上对齐的WORD,这意味着它们之间有2个字节的填充。
当我们切换到" unsigned int wWindow:16"时,编译器使用的是前一个位域的未使用部分,而不是单独的short,因此,没有浪费,没有short且在short之后没有填充,因此节省了四个字节。
我认为Mike B做对了,但不是很清楚。当我们要求" short"时,它在32位边界对齐。当我们要求int:16时,不是。所以int:16紧跟在ebit字段之后,而short跳过2个字节并从下一个32位块开始。
他所说的其余内容完全适用,因为绝对不能保证如何分配位字段,因此绝对不能使用位字段对外部可见的结构进行编码。充其量,它们属于嵌入式程序,在这些程序中,保存字节很重要。即使在那儿,我们也无法使用它们来实际控制内存映射端口中的位。