C++ 記憶體對齊

劍西樓發表於2017-02-11

注:本文程式碼測試環境為win7 X64 cpu, 編譯器為gcc4.7.1 和 vs2010

 

記憶體對齊是編譯器為了便於CPU快速訪問而採用的一項技術

我們先從一個例子開始,對下面的類(或者結構體)

class node

{

char c;

int i;

short s;

}no;

sizeof(no)的值是多少呢,如果你的回答是7(1+4+2),那麼你應該認真閱讀下面的內容。可以在編譯器上試試,輸出的結果是12,這就是記憶體對齊的結果。

 

為什麼要進行記憶體對齊呢?

  1. 平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。
  2. 效能原因:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。                                                                       本文地址

編譯器一般按照幾個位元組對齊呢?本文中兩個編譯器預設按照類中最大型別長度來對齊,我麼也可以使用語句#pragma pack(i)(i = 1,2,4,8,16)來設定對齊位元組數目,vs還可以在專案屬性-配置屬性-c/c++-程式碼生成-結構成員對齊設定。

 

對齊規則如下:

  1. 如果設定了記憶體對齊為 i 位元組,類中最大成員對齊位元組數為j,那麼整體對齊位元組n = min(i, j)  (某個成員的對齊位元組數定義:如果該成員是c++自帶型別如int、char、double等,那麼其對齊位元組數=該型別在記憶體中所佔的位元組數;如果該成員是自定義型別如某個class或者struct,那個它的對齊位元組數 = 該型別內最大的成員對齊位元組數《詳見例項4》)
  2. 每個成員對齊規則:類中第一個資料成員放在offset為0的位置;對於其他的資料成員(假設該資料成員對齊位元組數為k),他們放置的起始位置offset應該是 min(k, n) 的整數倍
  3. 整體對齊規則:最後整個類的大小應該是n的整數倍
  4. 當設定的對齊位元組數大於類中最大成員對齊位元組數時,這個設定實際上不產生任何效果(例項2);當設定對齊位元組數為1時,類的大小就是簡單的把所有成員大小相加

我們通過以下幾個例項來分析

例項1:(沒有指定對齊位元組,則n = 最大成員(int i)的大小4)

class node

{

char c;   //放在位置0,位置區間[0]

int i;      //4 = n, 那麼放置起始位置應該是4的倍數,即4,位置區間為[4~7]

short s; //2 < n,那麼放置起始位置應該是2的倍數,即8,位置區間為[8~9]

}

此時成員共佔用[0~9]10個位元組,還要整體對齊,大小應該是4的倍數,即12

 

例項2:(假設指定對齊位元組為8,那麼n = min(8,4) = 4)

class node

{

int i; //放在位置0,位置區間[0~3]

char c; //1 < n, 那麼放置起始位置應該是1的倍數,即4,位置區間為[4]

short s; //2 < n,那麼放置起始位置應該是2的倍數,即6,位置區間為[6~7]

}

成員共佔據[0~7]8個位元組,剛好是4的倍數,因此大小是8

 

例項3:(假設指定對齊位元組是2,則n = min(2,4) = 2)

class node

{

char c; //放在位置0,位置區間[0]

int i; //4 > n, 那麼放置起始位置應該是2的倍數,即2,位置區間為[2~5]

short s; //2 = n,那麼放置起始位置應該是2的倍數,即6,位置區間為[6~7]

}

此時成員共佔用[0~7]8個位元組,剛好是4的倍數,因此大小是8

 

例項4:(按照預設設定)

class temp 

    char c; 
    int i; 
    short s1; 
};

由例項1可知,預設對齊情況下,temp的大小是12,temp的對齊位元組數是:三個成員取最大的,即為4;

對於node,n = 其三個成員對齊位元組數取最大,即等於t的對齊位元組數,也就是 4。

class node

{

char c; //放在位置0,位置區間[0]

temp t; //4(temp的對齊位元組數) = n, 那麼放置起始位置應該是4的倍數,即4,位置區間為[4~15]

short s; //2 < n,那麼放置起始位置應該是2的倍數,即16,位置區間為[16~17]

}

此時成員共佔用[0~17]18個位元組,還要整體對齊,大小應該是4的倍數,因此大小是20

 

例項5:(默然設定)

對於node,n = 其三個成員對齊位元組數取最大,即等於d的對齊位元組數,也就是 8。

class node

{

temp t; //放在位置0,位置區間[0~11]

double d; //8(temp的對齊位元組數) = n, 那麼放置起始位置應該是8的倍數,即16,位置區間為[16~23]

short s; //2 < n,那麼放置起始位置應該是2的倍數,即24,位置區間為[24~25]

}

此時成員共佔用[0~25]26個位元組,還要整體對齊,大小應該是8的倍數,因此大小是32.

 


類繼承時的記憶體對齊

考慮如下類

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++物件模型》第三章

 

參考資料:

zhyjunfov的ChinaUnix部落格:gcc的記憶體對齊

【版權宣告】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3590491.html

相關文章