2012年2月16日 星期四

#pragma pack()

在這裡我先解釋"#pragma"的作用,"#pragma"就像是compiler的功能選擇開關,也就是#pragma是用來設定complier的選項,跟你在complier後加 -Zp -Xk 等等設定是一樣的。使用它其實就如同你在dos下使用compiler時,在命令列輸入那些參數一樣,所達到的功能是一致的,像在 Watcom C/C++ 中 "#pragma off (check_stack)", 代表的是把WC++的stack檢查關閉,當然你也可以從命令列輸入參數做到一樣的功能,不過有點不一樣的,用"#pragma",你可以選擇所需要這麼處理的程式區段,所以比命令列更方便更好用.不過"#pragma"所提供的功能切換在不同的compiler上並不相同,所以要特別注意.

接下來進入主題,到底"#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 bytes
(假設一開始是對齊的),然後才配置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 當然就是恢復原來的設定了。


沒有留言:

張貼留言

How to repair and clone disk with ddrescue

  ddrescue  is a tool that can be used to repair and clone disks on a  Linux system . This includes hard drives, partitions, DVD discs, flas...