通过改变窗口大小获得不同的标题大小

7

我有一个用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机器上。
9个回答

6
因为编译器将您的位域打包到32位int中,而不是16位实体。
通常情况下,应避免使用位域,并使用其他明确的位掩码和移位的显式位掩码(枚举等)来访问字段中的“子字段”。
以下是应避免使用位域的一个原因-即使在同一平台上,它们在编译器之间也不太可移植。从C99标准开始(C90标准中有类似的措辞):
实现可以分配任何足够大的可寻址存储单元来容纳位域。如果还有足够的空间,则立即跟随结构中另一个位域的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则无法放入不适合的位域,无论是将其放入下一个单元还是与相邻单元重叠都是实现定义的。位域在单位内的分配顺序(高位到低位或低位到高位)是实现定义的。可寻址存储单元的对齐方式未指定。
您无法保证位域是否会“跨越”int边界,也无法指定位域是从int的低端还是高端开始(这与处理器是大端还是小端无关)。

我想知道是否有人会定义一种便携式的位域指定方式,至少在POSIX兼容的硬件上,例如使用类似于“uInt32_t thing1, thing2; field1 : thing1.28.4; field2 : thing1.0.28; field3 : thing2.12.20;”这样的语法?这样的字段声明不会分配空间,但会访问先前分配的字段。我见过具有静态声明变量上叠加位标志命名法的编译器;我想知道是否有任何语法可以在其他结构字段上叠加位域。 - supercat

4

您的“unsigned int:xx”位域系列仅使用了int中的16位。另外16位(2字节)是存在的,但未使用。这之后是无符号短整型,它在int边界上,然后是一个WORD,它在int边界上对齐,这意味着它们之间有2个字节的填充。

当您切换到“unsigned int wWindow:16”时,编译器使用先前位域的未使用部分,因此没有浪费、没有短整型,并且在短整型之后没有填充,因此您可以节省4个字节。


2

@andy:+1,可以在pack参数上加入#pragma push/pop来帮助他。 - user7116

0
编译器将非位域结构成员填充到32位——本机字对齐。要解决这个问题,在结构体之前加上#pragma pack(0),在之后加上#pragma pack()。

#pragma pack (0) 没有改变行为。 - Steve Wolfe

0

内存中的结构边界可以根据字段的大小和顺序由编译器进行填充。


0

在打包方面不是C/C++专家。但我想规范中有一条规则,即当非位域跟随位域时,无论它是否适合剩余空间,都必须对齐到字边界。通过将其设置为显式位向量,您可以避免此问题。

再次强调,这只是猜测和一点经验。


0
有趣 - 我认为“WORD”会被评估为“unsigned short”,所以你会在不止一个地方遇到这个问题。
此外,请注意,您需要处理任何超过8位的值的字节序问题。

0

你看到不同的值是因为编译器打包规则不同。你可以在这里查看特定于Visual Studio的规则here

当你有一个必须打包(或遵守某些特定对齐要求)的结构时,你应该使用#pragma pack()选项。对于你的代码,你可以使用#pragma pack(0),它将使所有结构成员都对齐到字节边界。然后你可以使用#pragma pack()将结构打包重置为其默认状态。你可以在这里查看更多关于pack pragma的信息here


0

我认为Mike B说得对,但不是非常清楚。当你要求“short”时,它会对齐在32位边界上。当你要求int:16时,它不会。因此,int:16适合位字段之后,而short跳过2个字节,并从下一个32位块开始。

他所说的其余部分完全适用 - 位字段绝不能用于编码外部可见结构,因为无法保证它们的分配方式。最好的情况是,它们属于嵌入式程序,其中保存一个字节很重要。即使在那里,您也不能使用它们来实际控制内存映射端口中的位。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接