我有一个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”,它具有与unsigned short相同的位数,程序会告诉我结构的大小现在是20个字节,正确的大小。这是为什么?
我在32位x86计算机上使用Microsoft Visual Studio 2005 SP1。
答案 0 :(得分:6)
因为编译器正在将您的位域打包成32位的int,而不是16位的实体。
通常,您应该避免使用位域并使用其他显式常量(枚举或其他)与显式位屏蔽并移位以访问字段中的“子字段”。
这就是为什么应该避免使用位域的一个原因 - 即使对于同一平台,它们在编译器之间也不是很容易移植。来自C99标准(C90标准中的措辞类似):
实施可以分配任何 可寻址的存储单元足够大 持有一个位域。如果有足够的空间 仍然是一个立即的比特场 跟随a中的另一个位字段 结构应包装成 相同单元的相邻位。如果 空间不足,无论是否 放置不适合的位字段 进入下一个单位或重叠 相邻单位是 实现定义。的顺序 单位内的位字段分配 (高阶到低阶或低阶 到高阶)是 实现定义。对齐 可寻址存储单元的 未指定的。
您不能保证位字段是否会“跨越”int边界,并且您无法指定位域是从int的低端开始还是从int的高端开始(这与是否独立于处理器是big-endian或little-endian)。
答案 1 :(得分:4)
你的一系列“unsigned int:xx”位域只占用int中32位的16位。其他16位(2个字节)在那里,但未使用。接下来是无符号short,它位于int边界,然后是一个WORD,它在int边界上对齐,这意味着它们之间有2个字节的填充。
当您切换到“unsigned int wWindow:16”时,编译器使用前一个位域的未使用部分,而不是单独的短路,因此在短路后没有浪费,没有短路,也没有填充,因此可以节省四个字节。
答案 2 :(得分:2)
请参阅此问题:Why isn't sizeof for a struct equal to the sum of sizeof of each member?。
我相信当您使用“unsigned int wWindow:16”语法时,编译器会提示禁用填充。
另外,请注意,短路不能保证为16位。保证是:16位< =短的大小< = int的大小。
答案 3 :(得分:0)
编译器将非位域结构成员填充为32位 - 本机字对齐。要解决这个问题,请在struct之前执行#pragma pack(0),之后执行#pragma pack()。
答案 4 :(得分:0)
编译器可以填充内存中的结构边界,具体取决于字段的大小和顺序。
答案 5 :(得分:0)
在包装方面不是C / C ++专家。但是我想在规范中有一个规则,即当一个非位域跟随一个位域时,它必须在字边界上对齐,而不管它是否适合剩余的空间。通过使它成为一个明确的位向量,你可以避免这个问题。
再一次,这是一种经验的推测。
答案 6 :(得分:0)
有趣 - 我认为“WORD”会评价为“unsigned short”,所以你会在不止一个地方遇到这个问题。
另请注意,您需要处理8位以上的任何值的字节序问题。
答案 7 :(得分:0)
由于编译器打包规则,您看到了不同的值。您可以查看特定于visual studio here的规则。
如果您的结构必须打包(或遵守某些特定的对齐要求),则应使用#pragma pack()选项。对于您的代码,您可以使用#pragma pack(0),它将在字节边界上对齐所有结构成员。然后,您可以使用#pragma pack()将结构打包重置为默认状态。您可以在pack pragma here上看到更多信息。
答案 8 :(得分:0)
我认为迈克B做对了,但不是很清楚。当你要求“短”时,它在32位边界上对齐。当你要求int:16时,它不是。所以int:16适合在ebit字段之后,而short跳过2个字节并从下一个32位块开始。
他所说的其余部分完全适用 - 必须永远不要使用位字段来编码外部可见的结构,因为无法保证它们的分配方式。充其量,它们属于嵌入式程序,其中保存字节很重要。即使在那里,你也不能使用它们来实际控制内存映射端口中的位。