C/C++中结构可以是紧凑结构,也就是编译器不会自动向结构中插入任何填充字节,通常这可以用#pragma pack(1)来指定,这是MSVC定义的,但GCC、clang为了兼容都支持这个编译指示。
注意把填充(padding)和对齐(align)区分开来,对齐指的是某个变量或者结构中的成员变量的内存地址必须是2的幂的整数倍(比如2的整数倍),否则就是没有对齐要求。
用#pragma pack(push, 1)来指定结构的填充粒度,存在的问题是容易漏写与之配对的#pragma pack(pop),这会污染到所有后续定义的结构。所以是不推荐使用的,除非有工具能自动检测是否配对。
一、对于GCC、clang,优先使用__attribute__((packed))。这只对当前结构生效,不会污染后续定义的结构。
二、对于MSVC,推荐使用宏来保证#pragma pack(push, 1)和#pragma pack(pop)的配对。MSVC专门自定义了一个关键字__pragma,可以用在宏里面。
注意下面那个分号的位置。
#define PACKED( struct_to_pack ) __pragma( pack(push, 1) ) struct_to_pack __pragma( pack(pop) )
PACKED(
struct MyStruct {
uint8_t a;
uint8_t b;
uint8_t c;
uint16_t d;
uint32_t e;
}
);
三、对于其他支持C99的编译器,C99定义了一个关键字_Pragma。可以考虑用下面的宏。
注意下面两个分号的位置。最后一个分号可以不要。
#define PRAGMA(X) _Pragma(#X)
#define PRAGMA_PACK_PUSH(n) PRAGMA(pack(push,n))
#define PRAGMA_PACK_POP() PRAGMA(pack(pop))
#define PACKED(struct_to_pack) \
PRAGMA_PACK_PUSH(1) \
struct_to_pack \
PRAGMA_PACK_POP()
PACKED(
struct MyStruct {
uint8_t a;
uint8_t b;
uint8_t c;
uint16_t d;
uint32_t e;
};
);
四、重要的结构(一般是对外接口,比如硬件比特映射、磁盘文件结构映射、共享内存结构、网络二进制消息结构等),每个都用static_assert(sizeof(x) == y)保护一下。这样只要更换了软硬件平台、编译模式(比如32位和64位)等,可能会检测出老结构无法适配,需要人工重新审视。C11则用_Static_assert。
参考:
https://stackoverflow.com/questions/1537964/visual-c-equivalent-of-gccs-attribute-packed
https://docs.microsoft.com/en-us/cpp/preprocessor/pragma-directives-and-the-pragma-keyword?view=msvc-170
https://stackoverflow.com/questions/45130677/macro-definition-containing-pragma