在C++中,有没有一种方法可以控制结构体成员(包括位域)之间的填充?

12

我正在处理网络数据流解析,想知道是否有办法将数据流直接映射到数据结构。

例如,我想要定义RTP协议的数据结构如下。

class RTPHeader
{
   int version:2; // The first two bits is version.
   int P:1;  // The next bits is an field P.
   int X:1;
   int CC:4;
   int M:1;
   int PT:7;
   int sequenceNumber;
   int64 timestamp;
   .....
};

并且这样使用它。

RTPHeader header;
memcpy(&header, steamData, sizeof(header));

但是由于C++编译器会在成员之间插入填充字节,有没有办法控制它,使得成员之间不加入任何填充(包括位字段成员)?

这个问题不是重复的如何消除结构体数据成员之间的填充字节,因为我的示例中可能有位字段。


我相信没有标准的方法来做到这一点,但你的编译器可能有一些扩展(例如VS),可以让你控制填充。我希望有人能证明我是错误的。 - Cassio Neri
位域的长度加起来是8的倍数(毫不意外)。如果你指示编译器对类/结构体进行紧凑排列,那么应该没问题。 - Martin James
3
在位域之间不会添加填充,否则就没有使用位域的意义了。你认同吗? - Jonathan Potter
如果位域字段的总位数不是8的倍数,编译器将会进行填充,而不会在字节边界上切割后续的整数等内容。 - Martin James
@MartinJames 和 JonathanPotter,看起来即使位域加起来是8的倍数,也可以在它们之间添加填充:http://ideone.com/ipIcjH(但这里是因为两个连续成员无法适应单个U32)。 - gx_
显示剩余2条评论
3个回答

8
如果您能使用C++11,您可以利用使用alignof运算符实现的对齐控制。
如果您不能使用C++11编译器,有一些非标准的替代方法可以帮助您;在GCC中,使用__attribute__(packed),而在MSVC中,则使用#pragma pack
如果您选择了GCC变体,则该属性必须放置在结构的末尾
class RTPHeader
{
    int version:2; // The first two bits is version.
    int P:1;  // The next bits is an field P.
    int X:1;
    int CC:4;
    int M:1;
    int PT:7;
    int sequenceNumber;
    int64 timestamp;
    .....
} __attribute__((packed)) ; // attribute here!

如果你选择的是MSVC,那么#pragma必须放在结构体之前
#pragma pack(1) // pragma here!
class RTPHeader
{
    int version:2; // The first two bits is version.
    int P:1;  // The next bits is an field P.
    int X:1;
    int CC:4;
    int M:1;
    int PT:7;
    int sequenceNumber;
    int64 timestamp;
    .....
};

如果您的代码必须在两个平台上编译,那么唯一的方法(没有C++11 alignof运算符)就是条件编译:

#ifdef MSVC
#pragma pack(1)
#endif
class RTPHeader
{
    int version:2; // The first two bits is version.
    int P:1;  // The next bits is an field P.
    int X:1;
    int CC:4;
    int M:1;
    int PT:7;
    int sequenceNumber;
    int64 timestamp;
    .....
#ifdef GCC
}__attribute__((packed));
#else
};
#endif

3
为避免插入填充字节,您可以使用以下方法:
#pragma pack(push,n)   // use n = 1 to have 1 Byte resolution

typedef struct {...}MY_STRUCT;

#pragma pack(pop)

这对我来说很有效。

另外考虑结构成员对齐的编译选项。

/Zp1

但请记住,这将对您的整个项目产生影响。


2
然而,这是一个依赖于平台的编译器扩展(即使gcc和VS使用相同的语法,也不确定)。而且命令行选项绝对是只有VS才有的。 - Christian Rau

3
只要您没有要求此代码在任意机器上“运行” - 例如,有限制int所占字节边界的机器(通常为4个字节边界),那么使用它就可以了。
 #pragma(pack)

这应该可以工作,而且GCC也支持,还有微软和“与微软插件兼容”的编译器(例如英特尔的编译器)。

但请注意,并非所有处理器都支持未对齐访问,因此以16位值开头,后跟32位int的块可能会导致问题。

我还会使用大小整数来确保sequencenumber在每个编译器中都是32位,而不会突然变成16位或64位。

还要注意,C++标准没有规定位域中位的存储顺序 - 或者说它们之间是否有间隙。虽然您可以期望按字节顺序存储位域(小端机器从最低位开始,大端机器从最高位开始),但标准在这方面没有说明任何内容。


我将标记此帖子为答案,因为它不仅回答了问题,还指出了位域存储在C++中未定义以及小端问题。实际上,在考虑了端问题之后,我不想选择这种解决方案来解析流。 - ZijingWu

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