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