C/C++关于结构的紧凑填充的几条最佳实践

发布者:blowfish
发布于:2022-06-26 02:21

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


声明:该文观点仅代表作者本人,转载请注明来自看雪