C++ 中內存對齊原理及作用

struct/class/union內存對齊原則有四個:

1).數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數組,結構體等)的整數倍開始(比如int在32位機爲4字節, 則要從4的整數倍地址開始存儲),基本類型不包括struct/class/uinon。

2).結構體作爲成員:如果一個結構裏有某些結構體成員,則結構體成員要從其內部”最寬基本類型成員“的整數倍地址開始存儲.(struct a裏存有struct b,b裏有char,int ,double等元素,那b應該從8的整數倍開始存儲.)。

3).收尾工作:結構體的總大小,也就是sizeof的結果,.必須是其內部最大成員的”最寬基本類型成員”的整數倍.不足的要補齊.(基本類型不包括struct/class/uinon)。

4).sizeof(union),以結構裏面size最大元素爲union的size,因爲在某一時刻,union只有一個成員真正存儲於該地址。

實例解釋:下面以class爲代表

No.1

class Data
{
    char c;
    int a;
};

cout << sizeof(Data) << endl;

No.2

class Data
{
    char c;
    double a;
};

cout << sizeof(Data) << endl;

顯然程序No.1 輸出的結果爲 8 No.2 輸出的結果爲 16 .

No.1最大的數據成員是4bytes,1+4=5,補齊爲4的倍數,也就是8。而No.2爲8bytes,1+8=9,補齊爲8的倍數,也就是16。

No.3

class Data
{
    char c;
    int a;
    char d;
};

cout << sizeof(Data) << endl;

No.4

class Data
{
    char c;
    char d;
    int a;
};

cout << sizeof(Data) << endl;

No.3運行結果爲 12 No.4運行結果爲 8

class中的數據成員放入內存的時候,內存拿出一個內存塊來,數據成員們排隊一個一個往裏放,遇到太大的,不是把自己劈成兩半,能放多少放多少,而是等下一個內存塊過來。這樣的話,就可以理解爲什麼No.3,No.4兩端的代碼輸出結果不一樣了,因爲左邊是1+(3)+4+1+(3)=12,而右邊是1+1+(2)+4=8。括號中爲補齊的bytes。

No.5

class BigData
{
    char array[33];
};

class Data
{
    BigData bd;
    int integer;
    double d;
};

cout << sizeof(BigData) << "   " << sizeof(Data) << endl;

No.6

class BigData
{
    char array[33];
};

class Data
{
    BigData bd;
    double d;
};

cout << sizeof(BigData) << "   " << sizeof(Data) << endl;

No.5和No.6運行結果均爲: 48

在默認條件下,內存對齊是以class中最大的那個基本類型爲基準的,如果class中有自定義類型,則遞歸的取其中最大的基本類型來參與比較。在No.5和No.6中內存塊一個接一個的過來接走數據成員,一直到第5塊的時候,BigData裏只剩1個char了,將它放入內存塊中,內存塊還剩7個bytes,接下來是個int(4bytes),能夠放下,所以也進入第5個內存塊,這時候內存塊還剩3bytes,而接下來是個double(8bytes),放不下,所以要等下一個內存快到來。因此,No.5的Data的size=33+4+(3)+8=48,同理No.6應該是33+(7)+8=48。

順便提一下Union: 共用體表示幾個變量共用一個內存位置,在不同的時間保存不同的數據類型和不同長度的變量。在union中,所有的共用體成員共用一個空間,並且同一時間只能儲存其中一個成員變量的值。

No.7

class A
{
    int i;
    char c1;
}

class B:public A
{
    char c2;
}

class C:public B
{
    char c3;
}

sizeof(C)結果是多少呢,gcc和vs給出了不同的結果,分別是8、16

gcc中:C相當於把所有成員i、c1、c2、c3當作是在一個class內部,(先繼承後對齊)

vs中:對於A,對齊後其大小是8;對於B,c2加上對齊後的A的大小是9,對齊後就是12;對於C,c3加上對齊後的B大小是13,再對齊就是16 (先對齊後繼承)

關於c++對象繼承後的內存佈局,更詳細的分析可以《深度探索參考c++對象模型》第三章

內存對齊的主要作用是:

1、 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

2、 性能原因:經過內存對齊後,CPU的內存訪問速度大大提升。具體原因稍後解釋。

這裏寫圖片描述

這是普通程序員心目中的內存印象,由一個個的字節組成,而CPU並不是這麼看待的。

這裏寫圖片描述

CPU把內存當成是一塊一塊的,塊的大小可以是2,4,8,16字節大小,因此CPU在讀取內存時是一塊一塊進行讀取的。塊大小成爲memory access granularity(粒度) 本人把它翻譯爲“內存讀取粒度” 。

假設CPU要讀取一個int型4字節大小的數據到寄存器中,分兩種情況討論:

1、數據從0字節開始

2、數據從1字節開始

再次假設內存讀取粒度爲4。

這裏寫圖片描述

當該數據是從0字節開始時,很CPU只需讀取內存一次即可把這4字節的數據完全讀取到寄存器中。

當該數據是從1字節開始時,問題變的有些複雜,此時該int型數據不是位於內存讀取邊界上,這就是一類內存未對齊的數據。

這裏寫圖片描述

此時CPU先訪問一次內存,讀取0—3字節的數據進寄存器,並再次讀取4—5字節的數據進寄存器,接着把0字節和6,7,8字節的數據剔除,最後合併1,2,3,4字節的數據進寄存器。對一個內存未對齊的數據進行了這麼多額外的操作,大大降低了CPU性能。

這還屬於樂觀情況了,上文提到內存對齊的作用之一爲平臺的移植原因,因爲以上操作只有有部分CPU肯幹,其他一部分CPU遇到未對齊邊界就直接罷工了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章