這裡介紹 C 語言預處理指令 #pragma pack
所代表的意思,並以實際範例說明其使用上的效果與不使用的差異。
C 語言的 #pragma pack
是用來指定 struct
結構內部資料的儲存對齊方式的預處理指令,會直接影響 struct
結構所使用的記憶體空間大小,以及每個內部變數的放置位置,在處理低階資料結構(例如網路封包)時時常會需要使用到這個語法,以下是使用教學與實際範例。
struct
範例程式
以下是一個簡單的 struct
結構範例,我們定義一個 myStrA
這個 struct
,裡面包含不同長度的欄位,接著我們建立一個 myStrA
的測試變數,並直接將整個變數記憶體內部的資料直接以 16 進位輸出,觀察內部資料的儲存位置:
#include <stdio.h> typedef struct { unsigned char v1; unsigned int v2; unsigned long long v3; } myStrA; void main() { // 輸出每個變數類型的大小 printf("Size of unsinged char = %d\n", sizeof(unsigned char)); printf("Size of unsigned int = %d\n", sizeof(unsigned int)); printf("Size of unsigned long long = %d\n", sizeof(unsigned long long)); printf("Size of myStrA = %d\n", sizeof(myStrA)); // 建立方便辨識的測試資料 myStrA a; a.v1 = 0x11; a.v2 = 0x22334455; a.v3 = 0x66778899AABBCCDD; // 輸出 myStrA 內部實際的資料 unsigned char *ptr = (unsigned char *) &a; int i, s = sizeof(myStrA); printf("a = "); for (i = 0; i < s; ++i) { printf("%02X ", ptr[i]); } printf("\n"); }
使用 gcc
按照一般的的方式編譯並執行:
gcc -o pack pack.c ./pack
輸出如下:
Size of unsinged char = 1 Size of unsigned int = 4 Size of unsigned long long = 8 Size of myStrA = 16 a = 11 04 01 00 55 44 33 22 DD CC BB AA 99 88 77 66
我們的 myStrA
結構中,包含三個變數欄位,長度分別是 1
、4
與 8
,理論上它們只需要 1 + 4 + 8 = 13
個位元組就夠了,不過我們用 sizeof
查看 myStrA
大小的結果卻是 16
,多出了 3 個位元組。
最後我們將 a
變數記憶體中的實際原始資料直接印出來看,可以發現在第一個 unsigned char
的 v1
之後,多出了 3 個位元組,而其中的資料很奇怪(04 01 00
),這是因為編譯器在編譯時會將 struct
內部的變數「對齊」儲存,而比較短的變數後面就會出現這樣沒有用的記憶體空間,這些多出來的空間在整個程式執行時都不會用到,所以裡面的資料也都是沒有意義的,這就是所謂的記憶體對齊(alignment)。
使用記憶體對齊的好處就是存取效率較高,不過缺點就是會浪費記憶體空間。另外在某些應用上我們可能會需要把 struct
內所有的變數直接串起來,不要留下中間的空洞,這時候就可以使用 #pragma pack
這個預處理指令,它可以指定 C 語言編譯器在處理記憶體對齊時,所使用的記憶體封裝長度,單位為位元組(byte),可接受的值有 1
、2
、4
、16
,請參考 NSDN 的文件。
預處理指令的用法會跟編譯器有關,以 gcc 來說 #pragma pack
可設定最大可使用的記憶體封裝長度,請參考 Structure-Packing Pragmas)。
如果要在封裝記憶體時,完全不要留空洞,可以將記憶體封裝長度設為 1
:
// 加入 pack 設定 #pragma pack(1) typedef struct { unsigned char v1; unsigned int v2; unsigned long long v3; } myStrA;
這裡只是加上一行預處理指令,其餘的程式碼都不變,執行後結果就會變成:
Size of unsinged char = 1 Size of unsigned int = 4 Size of unsigned long long = 8 Size of myStrA = 13 a = 11 55 44 33 22 DD CC BB AA 99 88 77 66
這樣所有的資料在記憶體中就會是直接串接起來的狀況,不會有沒用到的記憶體空間。
#pragma pack
也可以設定其他的數值,以下是將記憶體封裝長度設為 2
的情況:
// 加入 pack 設定 #pragma pack(2) typedef struct { unsigned char v1; unsigned int v2; unsigned long long v3; } myStrA;
執行結果為:
Size of unsinged char = 1 Size of unsigned int = 4 Size of unsigned long long = 8 Size of myStrA = 14 a = 11 05 55 44 33 22 DD CC BB AA 99 88 77 66
union
範例
這個 #pragma pack
的語法也同樣適用於 union
與 class
。
#include <stdio.h> typedef union { unsigned short v1; struct { unsigned char c1; unsigned char c2; unsigned char c3; } v2; } myUniA; void main() { // 輸出每個變數類型的大小 printf("Size of unsinged char = %d\n", sizeof(unsigned char)); printf("Size of unsigned short = %d\n", sizeof(unsigned short)); printf("Size of myUniA = %d\n", sizeof(myUniA)); // 建立方便辨識的測試資料 myUniA a; a.v1 = 0x1122; a.v2.c3 = 0x33; // 輸出 myStrA 內部實際的資料 unsigned char *ptr = (unsigned char *) &a; int i, s = sizeof(myUniA); printf("a = "); for (i = 0; i < s; ++i) { printf("%02X ", ptr[i]); } printf("\n"); }
Size of unsinged char = 1 Size of unsigned short = 2 Size of myUniA = 4 a = 22 11 33 0E
加入一行 #pragma pack
,將記憶體封裝長度設為 1
:
// 加入 pack 設定 #pragma pack(1) typedef union { unsigned short v1; struct { unsigned char c1; unsigned char c2; unsigned char c3; } v2; } myUniA;
輸出為:
Size of unsinged char = 1 Size of unsigned short = 2 Size of myUniA = 3 a = 22 11 33
參考資料:iInfo 資訊交流、IBM Knowledge Center