天天看點

c/c++中記憶體對齊完全了解

一,什麼是記憶體對齊?記憶體對齊用來做什麼?

所謂記憶體對齊,是為了讓記憶體存取更有效率而采用的一種編譯階段優化記憶體存取的手段。

比如對于int x;(這裡假設sizeof(int)==4),因為cpu對記憶體的讀取操作是對齊的,如果x的位址不是4的倍數,那麼讀取這個x,需要讀取兩次共8個位元組,然後還要将其拼接成一個int,這比存取對齊過的x要麻煩很多。

二,怎麼算記憶體對齊大小(理論)?

對于簡單類型,如int,char,float等,其對齊大小為其本身大小,即align(int) == sizeof(int),align(char)==sizeof(char),等等。

對于複合類型,如struct,class,其本身并無所謂對齊,因為CPU沒有直接存取一個struct的指令。對于struct而言,它的對齊指的是它裡面的所有成員變量都是對齊的,class同理。

下面就講講struct對齊是怎麼回事。

首先要明白三個點:

1,記憶體對齊是指首位址對齊,而不是說每個變量大小對齊;

2,結構體記憶體對齊要求結構體内每一個成員變量都是記憶體對齊的;

3,結構體對齊除了第2點之外還要求結構體數組也必須是對齊的,也就是說每個相鄰的結構體内部都是對齊的。

OK,先知道上面這3點之後,開始接觸怎麼算對齊大小。

程式員可自己指定某些資料的對齊大小,通過使用下面的預處理指令,指定對齊大小為x。(這裡需要注意:隻能指定2的n次方作為對齊大小,對于指定對齊大小為6,9,10這樣的編譯器可能會不予理會)

#pragma pack(x)
//...
#pragma pack()      

那到現在,可能大家有個疑問了,那對于int(這裡假設sizeof(int)==4),手動指定對齊大小為8,那align(int)是等于sizeof(int)還是等于8呢 ?

這裡大家可以記住,align(x) = min ( sizeof(x) , packalign) , 即sizeof(x)和指定對齊大小哪個小,對齊大小就為哪個。

是以,上面的疑問答案是align(int)=sizeof(int)=4 。

三,怎麼算記憶體對齊大小(示範)?

#include <cassert>


int main(int argc, char* argv[])
{
    //此處指定對齊大小為1
    //對于a,實際對齊大小為min(sizeof(int),1)=min(4,1)=1
    //對于b,實際對齊大小為min(sizeof(char),1)=min(1,1)=1
    //編譯器會確定TEST_A首位址即a的地首址是1位元組對齊的,此時a對齊
    //對于b,由于b要求首位址1位元組對齊,這顯然對于任何位址都合适,是以a,b都是對齊的
    //對于TEST_A數組,第一個TEST_A是對齊的(假設其位址為0),則第二個TEST_A的首位址為(0+5=5),對于第二個TEST_A的兩個變量a,b均對齊
    //OK,對齊合理。是以整個結構體的大小為5
#pragma pack(1)
    struct TEST_A
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_A) == 5);

    //此處指定對齊大小為2
    //對于a,實際對齊大小為min(sizeof(int),2)=min(4,2)=2
    //對于b,實際對齊大小為min(sizeof(char),2)=min(1,2)=1
    //編譯器會確定TEST_A首位址即a的地首址是2位元組對齊的,此時a對齊
    //對于b,由于b要求首位址1位元組對齊,這顯然對于任何位址都合适,是以a,b都是對齊的
    //對于TEST_B數組,第一個TEST_B是對齊的(假設其位址為0),則第二個TEST_B的首位址為(0+5=5),對于第二個TEST_B的變量a,顯然位址5是不對齊于2位元組的
    //是以,需要在TEST_B的變量b後面填充1位元組,此時連續相連的TEST_B數組才會對齊
    //OK,對齊合理。是以整個結構體的大小為5+1=6
#pragma pack(2)
    struct TEST_B
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_B) == 6);

    //此處指定對齊大小為4
    //對于a,實際對齊大小為min(sizeof(int),2)=min(4,4)=4
    //對于b,實際對齊大小為min(sizeof(char),2)=min(1,4)=1
    //編譯器會確定TEST_A首位址即a的地首址是4位元組對齊的,此時a對齊
    //對于b,由于b要求首位址1位元組對齊,這顯然對于任何位址都合适,是以a,b都是對齊的
    //對于TEST_C數組,第一個TEST_C是對齊的(假設其位址為0),則第二個TEST_C的首位址為(0+5=5),對于第二個TEST_C的變量a,顯然位址5是不對齊于4位元組的
    //是以,需要在TEST_C的變量b後面填充3位元組,此時連續相連的TEST_C數組才會對齊
    //OK,對齊合理。是以整個結構體的大小為5+3=8
#pragma pack(4)
    struct TEST_C
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_C) == 8);

    //此處指定對齊大小為8
    //對于a,實際對齊大小為min(sizeof(int),8)=min(4,8)=4
    //對于b,實際對齊大小為min(sizeof(char),8)=min(1,8)=1
    //編譯器會確定TEST_A首位址即a的地首址是4位元組對齊的,此時a對齊
    //對于b,由于b要求首位址1位元組對齊,這顯然對于任何位址都合适,是以a,b都是對齊的
    //對于TEST_D數組,第一個TEST_D是對齊的(假設其位址為0),則第二個TEST_D的首位址為(0+5=5),對于第二個TEST_D的變量a,顯然位址5是不對齊于4位元組的
    //是以,需要在TEST_D的變量b後面填充3位元組,此時連續相連的TEST_D數組才會對齊
    //OK,對齊合理。是以整個結構體的大小為5+3=8
#pragma pack(8)
    struct TEST_D
    {
        int a;
        char b;
    };
#pragma  pack()
    assert(sizeof(TEST_D) == 8);


    //此處指定對齊大小為8
    //對于a,實際對齊大小為min(sizeof(int),8)=min(4,8)=4
    //對于b,實際對齊大小為min(sizeof(char),8)=min(1,8)=1
    //對于c,這是一個數組,數組的對齊大小與其單元一緻,因而align(c)=align(double)=min(sizeof(double),8)=min(8,8)=8
    //對于d,實際對齊大小為min(sizeof(char),8)=min(1,8)=1
    //編譯器會確定TEST_A首位址即a的地首址是4位元組對齊的,此時a對齊
    //對于b,由于b要求首位址1位元組對齊,這顯然對于任何位址都合适,是以a,b都是對齊的
    //對于c,由于c要求首位址8位元組對齊,是以前面的a+b=5,還要在c後面補上3個位元組才能對齊
    //對于d,顯而易見,任何位址均對齊,此時結構體大小為4+1+3+10*8+1=89
    //對于TEST_E數組,第一個TEST_E是對齊的(假設其位址為0),則第二個TEST_E的首位址為(0+89=89),對于第二個TEST_E的變量a,顯然位址89是不對齊于4位元組的
    //是以,需要在TEST_E的變量d後面填充7位元組,此時連續相連的TEST_E數組才會對齊 
    //(注意:此處不僅要確定下一個TEST_E的a,b變量對齊,還要確定c也對齊,是以這裡不是填充3位元組,而是填充7位元組)
    //OK,對齊合理。是以整個結構體的大小為(4)+(1+3)+(10*8)+(1+7)=96
#pragma pack(8)
    struct TEST_E
    {
        int a;
        char b;
        double c[10];
        char d;
    };
#pragma  pack()
    assert(sizeof(TEST_E) == 96);

    return 0;
}      

四,記憶體對齊相關

使用msvc未公開編譯選項可以檢視c++類的記憶體布局。使用方法:啟動vs指令行,輸入cl 【source.cpp】 /d1reportSingleClassLayout【CBaseClass1】以檢視單個class的記憶體布局,輸入cl 【source.cpp】 /d1reportAllClassLayout以檢視所有類的記憶體布局。注意:/d1reportSingleClassLayout【CBaseClass1】沒有空格 !!

大家可以用這個來對照我上面講的例子來看編譯器是怎麼安排對齊的。

這個東東是神器,類似于宏展開時的選項(輸出與處理過之後的源檔案),一切内部布局方面的真相全都展現在你眼前,包括坑腦細胞的虛函數、虛函數表、虛基類表、虛繼承等一系列坑爹。

五,參考資料:

1,http://blog.csdn.net/arethe/article/details/2548867

2,http://msdn.microsoft.com/en-us/library/83ythb65.aspx

3,http://msdn.microsoft.com/en-us/library/9dbwhz68.aspx

4,http://msdn.microsoft.com/en-us/library/71kf49f1.aspx

5,http://blog.sina.com.cn/s/blog_67c294ca01012qbu.html

如果你喜歡這篇文章,歡迎推薦!

開放,共享,網際網路的未來 !

技術改變世界,我是程式員,我喂自己袋鹽 !

c/c++中記憶體對齊完全了解

繼續閱讀