2014年3月17日 星期一

#pragma pack

src:
http://www.cplusplus.com/forum/general/14659/
http://blog.xuite.net/jackie.xie/bluelove/7788211

notes:
14'3/17 :
[1]
- For linux [declared in data structure only ? In one line]
__attribute__ ((packed))

- For Window [declared by range]
#pragma pack...

#pragma pack...


[2] declaration of #pragma
- format-1
#pragma pack(1 )  [begin]
#pragma pack( )  [end]

- format-2 (it's better?)
#pragma pack(push, 1)
#pragma pack(pop)


----------------
(Very ) Basically the compiler unless otherwise directed will line up structure members on
2 byte or 4 byte boundaries - this makes it easier and faster for the processor to handle.
So the structure contains secret padding bytes to make this happen.

The pragma pack directive (in MS Compiler) allows you to change this alignment scheme.

Some things (particularly in relation to hardware) do not have the luxury to waste bytes like
this and they send their data in an exact fit.
This means that it is not wise to read data from a hardware device directly into a normal structure.

If you have want to read data that is an exact fit into a structure - you can tell the compiler
to make the structure an exact fit like this (microsoft compiler);
1
2
3
4
5
6
7
8
#pragma pack(push, 1) // exact fit - no padding
struct MyStruct
{
  char b; 
  int a; 
  int array[2];
};
#pragma pack(pop) //back to whatever the previous packing mode was 


Without the pragma directive, the size of the structure is 16 bytes - with the packing of 1 - the size is 13 bytes


----------------

#pragma pack(1) "是表示什麼意思呢?!

在這裡我先解釋"#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 <stdio.h>
#include <conio.h>
#include <windows.h>
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 當然就是恢復原來的設定了。

沒有留言:

張貼留言