現代主流C和C++編譯器MSVC與GCC均支援使用 #pragma pack 來指定某組結構體或聯合體(C++還可支援類)的每一個成員的最大對齊位元組數。這在某些諸如可跨平台的對象序列化等應用場合會比較有用。下面筆者将介紹GCC與MSVC均能支援的 #pragma pack 的幾種形式。
- #pragma pack( push [ , n ] ):表示先将目前pack對齊的設定儲存在目前編譯器棧上,然後用 n 來設定新的pack對齊要求。n是可預設的。如果預設,那麼以目前編譯器預設設定的pack對齊方式進行設定。
- #pragma pack( pop ):表示将目前pack對齊的設定推出編譯器對pack對齊設定的棧,而回到前一個設定。
- #pragma pack( [ n ] ):表示用 n 來設定新的pack對齊設定。n是可預設的。如果預設,那麼以目前編譯器預設設定的pack對齊方式進行設定。
下面提供一些示例代碼為大家提供一個更好的直覺感受。以下代碼可在Visual Studio 2019或更高版本上的MSVC(通過将C語言編譯選項設定為C11或C17)以及GCC 4.8以上、Clang 3.6以上通過編譯和運作。
#include <stdio.h>
#include <stdalign.h>
#pragma pack(2)
typedef struct S1
{
char c;
int i;
} S1;
#pragma pack(push, 1)
typedef struct S2
{
char c;
int i;
} S2;
typedef struct S3
{
char c;
long long ll;
} S3;
// 恢複為之前pack設定
#pragma pack(pop)
typedef struct S4
{
char c;
long long ll;
} S4;
// 恢複為目前編譯器預設的pack設定
#pragma pack()
typedef struct S5
{
char c;
int i;
} S5;
int main(void)
{
// 輸出:S1 size: 6, alignment: 2, c offset: 0, i offset: 2
printf("S1 size: %zu, alignment: %zu, c offset: %zu, i offset: %zu\n",
sizeof(S1), alignof(S1), offsetof(S1, c), offsetof(S1, i));
// 輸出:S2 size: 5, alignment: 1, c offset: 0, i offset: 1
printf("S2 size: %zu, alignment: %zu, c offset: %zu, i offset: %zu\n",
sizeof(S2), alignof(S2), offsetof(S2, c), offsetof(S2, i));
// 輸出:S3 size: 9, alignment: 1, c offset: 0, i offset: 1
printf("S3 size: %zu, alignment: %zu, c offset: %zu, i offset: %zu\n",
sizeof(S3), alignof(S3), offsetof(S3, c), offsetof(S3, ll));
// 輸出:S4 size: 10, alignment: 2, c offset: 0, i offset: 2
printf("S4 size: %zu, alignment: %zu, c offset: %zu, i offset: %zu\n",
sizeof(S4), alignof(S4), offsetof(S4, c), offsetof(S4, ll));
// 輸出:S5 size: 8, alignment: 4, c offset: 0, i offset: 4
printf("S5 size: %zu, alignment: %zu, c offset: %zu, i offset: %zu\n",
sizeof(S5), alignof(S5), offsetof(S5, c), offsetof(S5, i));
}
此外,為了能更友善地為單個結構體等複合類型設定每個成員最大隻能以1個位元組對齊的設定,GCC和Clang分别提供了擴充文法進行設定。
以下為GCC的文法擴充:
struct __attribute__((packed)) S
{
char c;
int i;
};
以下為Clang編譯器所支援的文法擴充:
struct [[gnu::packed]] S
{
char c;
int i;
};
請注意,Clang的該文法擴充隻能用于C++11開始起的C++編譯器以及可支援C2X标準的C語言編譯器。印象中,Clang 9開始應該能支援 -std=gnu2x 這一編譯選項。是以如果各位在Clang上用的是C編譯器的話,需要顯式使用該選項。