#pragma pack()用法詳解


博客轉載自:http://blog.csdn.net/lime1991/article/details/44536343

1.什么是對齊?為什么要對齊?

現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
各個硬件平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的數據只能從某些特定地址開始存取。其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會需要2個讀周期,並對兩次讀出的結果的高低字節進行拼湊才能得到該int數據。顯然在讀取效率上下降很多。這也是空間和時間的博弈。

2.pragma pack語法

用sizeof運算符求算某結構體所占空間時,並不是簡單地將結構體中所有元素各自占的空間相加,這里涉及到內存字節對齊的問題,有時候為了內存對齊需要補齊空字節。通常寫程序的時候,不需要考慮對齊問題。編譯器會替我們選擇適合目標平台的對齊策略。當然,我們也可以通知給編譯器傳遞預編譯指令而改變對指定數據的對齊方法。
語法:#pragma pack( [show] | [push | pop] [, identifier], n )
作用:指定結構,聯合和類的包對齊方式(pack alignment)
show(optical):顯示當前packing aligment的字節數,以warning message的形式顯示。
push(optical):
Pushes the current packing alignment value on the internal compiler stack, and sets the current packing alignment value to n. If n is not specified, the current packing alignment value is pushed.
pop(optical):
Removes the record from the top of the internal compiler stack. If n is not specified with pop, then the packing value associated with the resulting record on the top of the stack is the new packing alignment value. If n is specified, for example, #pragma pack(pop, 16), n becomes the new packing alignment value. If you pop with identifier, for example, #pragma pack(pop, r1), then all records on the stack are popped until the record that hasidentifier is found. That record is popped and the packing value associated with the resulting record on the top of is the stack the new packing alignment value. If you pop with an identifier that is not found in any record on the stack, then the pop is ignored.
identifier(optional):
When used with push, assigns a name to the record on the internal compiler stack. When used with pop, pops records off the internal stack until identifieris removed; if identifier is not found on the internal stack, nothing is popped.
n (optional):
Specifies the value, in bytes, to be used for packing. If the compiler option /Zp is not set for the module, the default value for n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.

3.結構體對齊規則

結構體中各個成員按照它們被聲明的順序在內存中順序存儲。
1)將結構體內所有數據成員的長度值相加,記為sum_a; 
2)將各數據成員內存對齊,按各自對齊模數而填充的字節數累加到和sum_a上,記為sum_b。對齊模數是【該數據成員所占內存】與#pragma pack指定的數值】中的較小者。
3)將和sum_b向結構體模數對齊,該模數是【#pragma pack指定的數值】、【未指定#pragma pack時,系統默認的對齊模數8字節】和【結構體內部最大的基本數據類型成員】長度中數值較小者。結構體的長度應該是該模數的整數倍。

3.1 基本數據類型所占內存大小

以下例子均按32bit編譯器處理。

3.2 Test1

#pragma pack(4)  
struct Test1  
{  
    char c;  
    short sh;  
    int a;  
    float f;  
    int *p;  
    char *s;  
    double d;  
};  

總共占28Bytes。 c的偏移量為0,占1個Byte。sh占2個Byte,它的對齊模數是2(2<4,取小者),存放起始地址應該是2的整數倍,因此c后填充1個空字符,sh的起始地址是2。a占4個Byte,對齊模數是4,因此接在sh后存放即可,偏移量為4。f占4個字節,對齊模數是4,存放地址是4的整數倍,起始地址是8。p,s的起始地址分別是12,16。d占8個字節,對齊模數是4(4<8),d從偏移地址為20處存放。存放后結構體占28個字節,是4的整數倍不用補空字符。

 

struct Test2  
{  
    char c;  
    double d;  
    int a;  
    short sh;  
    float f;  
    int *p;  
    char *s;          
};  

將Test1個變量的順序換一下位置,結構體Test2占用內存32Byte,可見寫結構體時,將各個變量按所占內存從小到大排列所占結構體所占內存較小。

3.3關於靜態變量static

靜態變量的存放位置與結構體實例的存儲地址無關,是單獨存放在靜態數據區的,因此用siezof計算其大小時沒有將靜態成員所占的空間計算進來。

#pragma pack(4)  
struct Test3  
{  
    char c;  
    short sh;  
    int a;  
    float f;  
    int *p;  
    char *s;  
    double d;  
    static double sb;  
    static int sn;  
}; 

sizeof(Test3)=28

3.4關於類

空類是會占用內存空間的,而且大小是1,原因是C++要求每個實例在內存中都有獨一無二的地址。

(一)類內部的成員變量:

  • 普通的變量:是要占用內存的,但是要注意對齊原則(這點和struct類型很相似)。
  • static修飾的靜態變量:不占用內容,原因是編譯器將其放在全局變量區。

(二)類內部的成員函數:

  • 普通函數:不占用內存。
  • 虛函數:要占用4個字節,用來指定虛函數的虛擬函數表的入口地址。所以一個類的虛函數所占用的地址是不變的,和虛函數的個數是沒有關系的
    #pragma pack(4)  
    class cBase{};  

    sizeof(cBase)=1

  • 3.4.1 不包含虛函數的類
    #pragma pack(4)  
    class CBase1  
    {  
    private:  
        char c;  
        short sh;  
        int a;  
    public:  
        void fOut(){ cout << "hello" << endl; }  
    };  

    不包含虛函數時,對於類中的成員變量按結構體對齊方式處理,普通函數函數不占內存。sizeof(CBase1)=8

    3.4.2 包含虛函數的類

    #pragma pack(4)  
    class CBase2  
    {  
    private:  
        char c;  
        short sh;  
        int a;  
    public:  
        virtual void fOut(){ cout << "hello" << endl; }  
    }; 

     包含虛函數時,類中需要保存虛函數表的入口地址指針,即需要多保存一個指針。這個值跟虛函數的個數多少沒有關系。sizeof(CBase2)=12

    3.4.3 子類

    子類所占內存大小是父類+自身成員變量的值。特別注意的是,子類與父類共享同一個虛函數指針,因此當子類新聲明一個虛函數時,不必在對其保存虛函數表指針入口。

    #pragma pack(4)  
    class CBase2  
    {  
    private:  
        char c;  
        short sh;  
        int a;  
    public:  
        virtual void fOut(){ cout << "virtual 1" << endl; }  
    };  
    class cDerive :public CBase  
    {  
    private :  
        int n;  
    public:  
        virtual void fPut(){ cout << "virtual 2"; }  
    };

    sizeof(cDerive)= sizeof(cBase)+sizeof(int n) = 16


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM