接下來進入主題,到底"#pragma pack(1)"是做什麼的?其實"#pragma pack( )"就是要compiler照我們的意思去做記憶體aligment(對齊)的工作,在Win32下為了提高記憶體存取效率,所以記憶體配置(對齊)的預設值是以DWORD(4BYTES也就是32bits)為單位,因此在配置struct的記憶體空間的時候,往往會造成struct內部的資料在記憶體位址上並不連續,在我們的程式之中,如果不是用指標去存取這些資料,可能還不會出錯,可是如果我們真的用指標去存取的話,由於資料排列不連續,我們可能會讀不到我們想要的值,這還不打緊,如果這個結構是傳址給Win API來取得資料,那就問題大條了,因為API會以為struct內部的記憶體位址是連續的,所以會造成錯誤,像我之前寫"在Win95/98下存取邏輯磁區"那篇文章的範例程式時,就為此吃足苦頭,不但結果不正確,還一直當機,後來才發現是記憶體aligment的問題,為了讓struct內部的記憶體位址是連續的,這時我們就必需要用"#pragma pack(1)"這個命令,來讓compiler把這個struct,以BYTE為單位做記憶體aligment,如此一來struct使用時就不會出錯了.
"#pragma pack()"有幾個值可供設定,如下所述,"#pragma pack(1)"是對齊BYTE,"#pragma pack(2)"是對齊WORD,而"#pragma pack( )"等於"#pragma pack(4)",就是對齊DWORD,另外,還有"#pragma pack(8)",以及"#pragma pack(16)"可以選用,所以我們只要將struct的定義區段,用"#pragma pack(1)"和"#pragma pack( )"包起來,就可以了使struct正確被存取了.以下是個示範aligment的程式:
////本程式在BCB5中順利編譯執行
#include
#include
#include
int main(int argc, char* argv[])
{
#pragma pack(4)
////記憶體對齊單位用DWORD
typedef struct _aligment_DWORD{
char a ;
WORD test1;
char b ;
char c ;
char d ;
} aligment_DWORD;
#pragma pack( )
#pragma pack(1)
////記憶體對齊單位用BYTE
typedef struct _aligment_BYTE{
char e ;
WORD test2 ;
char f ;
char g ;
char h ;
} aligment_BYTE;
#pragma pack( )
aligment_BYTE ali_byte ;
aligment_DWORD ali_dw ;
ali_dw.a = 'A' ;
ali_dw.b = 'B' ;
ali_dw.c = 'C' ;
ali_dw.d = 'D' ;
ali_byte.e = 'E' ;
ali_byte.f = 'F' ;
ali_byte.g = 'G' ;
ali_byte.h = 'H' ;
LPTSTR pointer ;
printf("aligment_DWORD struct is %d BYTES \n",sizeof(ali_dw));
printf("aligment_BYTE struct is %d BYTES\n",sizeof(ali_byte));
pointer = (LPTSTR)(&ali_byte) ;
//請注意記憶體的位址,和輸出的結果之間的關係
printf("ali_byte.e is %c \n",*pointer);
printf("ali_byte.f is %c \n",*(pointer + 3 ));
printf("ali_byte.g is %c \n",*(pointer + 4 ));
printf("ali_byte.h is %c \n",*(pointer + 5 ));
pointer = (LPTSTR)(&ali_dw) ;
printf("ali_byte.a is %c \n",*pointer);
printf("ali_byte.b is %c \n",*(pointer + 4 ));
printf("ali_byte.c is %c \n",*(pointer + 5 ));
printf("ali_byte.d is %c \n",*(pointer + 6 ));
getch();
return 0;
}
////輸出結果如下
aligment_DWORD struct is 8 BYTES
aligment_BYTE struct is 6 BYTES
ali_byte.e is E
ali_byte.f is F
ali_byte.g is G
ali_byte.h is H
ali_byte.a is A
ali_byte.b is B
ali_byte.c is C
ali_byte.d is D
//////////////////////////////////
讓我們來看上面這個程式還有它的輸出結果,程式中的兩個struct內容總共都是 6 BYTES,可是由於aligment設定的不同,一個size是正常的6 BYTES,另一個竟然變成是8 BYTES,為了輸出正確結果,那個aligment設為DWORD的struct,在printf( )中的位址變得很奇怪吧!!
所以如果我們要避免struct在Win32的程式中出錯,以後可別忘了加上"#pragma pack(1)"和"#pragma pack( )" !
#pragma pack(n)是用來讓struct的成員對齊記憶體用的,在32bit系統下基於處理器效率的考量,由於預設的對齊位置是4 bytes,所以所有的struct成員視為 #pragma pack(4),但有時候我們希望struct裡成員是連績的,尤其是控制硬體相關的io位置,所以會設為#paragma pack(1),讓struct的成員要用 Byte 來對齊。看以下例子:
struct pci_conf {
WORD VendorID;
WORD DeviceID;
...
} pci;
如果沒有用#pragma pack(1),VendorID後會空2 bytes不用,以便對到4 bytesWORD VendorID;
WORD DeviceID;
...
} pci;
(假設一開始是對齊的),然後才配置DeviceID。
可是若沒留意這樣的問題,
(BYTE*) p = &pci;
預期*(p+0x2)是DeviceID就會出問題。
#pragma pack和數據對齊問題
結構數據存放時預設是按4 bytes對齊,考慮以下程序,輸出結果為:sizeof(A)=12
typedef struct _A
{
int x;
char z[7];
}A;
void main()
{
int len = sizeof(A);
printf("sizeof(A)=%d\n", len); // len = 12
}
使用 #pragma pack,設置數據按1對齊,此時輸出結果為:sizeof(A)=11
#pragma pack(push)
#pragma pack(1)
typedef struct _A
{
int x;
char z[7];
}A;
#pragma pack(pop)
void main()
{
int len = sizeof(A);
printf("sizeof(A)=%d\n", len); // len = 11
}
其中 :
push 就是在改變為 1 之前先儲存原來的設定。
pop 當然就是恢復原來的設定了。
pop 當然就是恢復原來的設定了。