C/C++記憶體對齊詳解

俊華的部落格發表於2021-01-19

1、什麼是記憶體對齊

還是用一個例子帶出這個問題,看下面的小程式,理論上,32位系統下,int佔4byte,char佔一個byte,那麼將它們放到一個結構體中應該佔4+1=5byte;但是實際上,通過執行程式得到的結果是8 byte,這就是記憶體對齊所導致的。

//32位系統
#include<stdio.h>
struct{
    int x;
    char y;
}s;

int main()
{
    printf("%d\n",sizeof(s);  // 輸出8
    return 0;
}

 

現代計算機中記憶體空間都是按照 byte 劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何地址開始,但是實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,這就是所謂的記憶體對齊。

2、為什麼要進行記憶體對齊

儘管記憶體是以位元組為單位,但是大部分處理器並不是按位元組塊來存取記憶體的.它一般會以雙位元組,四位元組,8位元組,16位元組甚至32位元組為單位來存取記憶體,我們將上述這些存取單位稱為記憶體存取粒度.

現在考慮4位元組存取粒度的處理器取int型別變數(32位系統),該處理器只能從地址為4的倍數的記憶體開始讀取資料。

假如沒有記憶體對齊機制,資料可以任意存放,現在一個int變數存放在從地址1開始的連續四個位元組地址中,該處理器去取資料時,要先從0地址開始讀取第一個4位元組塊,剔除不想要的位元組(0地址),然後從地址4開始讀取下一個4位元組塊,同樣剔除不要的資料(5,6,7地址),最後留下的兩塊資料合併放入暫存器.這需要做很多工作.

簡單的說記憶體對齊能夠提高 cpu 讀取資料的速度,減少 cpu 訪問資料的出錯性(有些 cpu 必須記憶體對齊,否則指標訪問會出錯)

 

C/C++記憶體對齊詳解

現在有了記憶體對齊的,int型別資料只能存放在按照對齊規則的記憶體中,比如說0地址開始的記憶體。那麼現在該處理器在取資料時一次性就能將資料讀出來了,而且不需要做額外的操作,提高了效率。

C/C++記憶體對齊詳解

3、記憶體對齊規則

每個特定平臺上的編譯器都有自己的預設“對齊係數”(也叫對齊模數)。gcc中預設#pragma pack(4),可以通過預編譯命令#pragma pack(n),n = 1,2,4,8,16來改變這一系數。

有效對其值:是給定值#pragma pack(n)和結構體中最長資料型別長度中較小的那個。有效對齊值也叫對齊單位。

瞭解了上面的概念後,我們現在可以來看看記憶體對齊需要遵循的規則:

(1) 結構體第一個成員的偏移量(offset)為0,以後每個成員相對於結構體首地址的 offset 都是該成員大小與有效對齊值中較小那個的整數倍,如有需要編譯器會在成員之間加上填充位元組。

(3) 結構體的總大小為 有效對齊值 的整數倍,如有需要編譯器會在最末一個成員之後加上填充位元組。

下面給出幾個例子以便於理解:

//32位系統
#include<stdio.h>
struct
{
    int i;    
    char c1;  
    char c2;  
}x1;

struct{
    char c1;  
    int i;    
    char c2;  
}x2;

struct{
    char c1;  
    char c2; 
    int i;    
}x3;

int main()
{
    printf("%d\n",sizeof(x1));  // 輸出8
    printf("%d\n",sizeof(x2));  // 輸出12
    printf("%d\n",sizeof(x3));  // 輸出8
    return 0;
}

 

以上測試都是在Linux環境下進行的,linux下預設#pragma pack(4),且結構體中最長的資料型別為4個位元組,所以有效對齊單位為4位元組,下面根據上面所說的規則以s2來分析其記憶體佈局:

首先使用規則1,對成員變數進行對齊:

sizeof(c1) = 1 <= 4(有效對齊位),按照1位元組對齊,佔用第0單元;

sizeof(i) = 4 <= 4(有效對齊位),相對於結構體首地址的偏移要為4的倍數,佔用第4,5,6,7單元;

sizeof(c2) = 1 <= 4(有效對齊位),相對於結構體首地址的偏移要為1的倍數,佔用第8單元;

然後使用規則2,對結構體整體進行對齊:

s2中變數i佔用記憶體最大佔4位元組,而有效對齊單位也為4位元組,兩者較小值就是4位元組。因此整體也是按照4位元組對齊。由規則1得到s2佔9個位元組,此處再按照規則2進行整體的4位元組對齊,所以整個結構體佔用12個位元組。

根據上面的分析,不難得出上面例子三個結構體的記憶體佈局如下:

C/C++記憶體對齊詳解

#pragma pack(n)

不同平臺上編譯器的 pragma pack 預設值不同。而我們可以通過預編譯命令#pragma pack(n), n= 1,2,4,8,16來改變對齊係數。

例如,對於上個例子的三個結構體,如果前面加上#pragma pack(1),那麼此時有效對齊值為1位元組,此時根據對齊規則,不難看出成員是連續存放的,三個結構體的大小都是6位元組。

C/C++記憶體對齊詳解

如果前面加上#pragma pack(2),有效對齊值為2位元組,此時根據對齊規則,三個結構體的大小應為6,8,6。記憶體分佈圖如下:

C/C++記憶體對齊詳解

經過上面的例項分析,大家應該對記憶體對齊有了全面的認識和了解,在以後的編碼中定義結構體時需要考慮成員變數定義的先後順序了。

參考資料:
http://light3moon.com/2015/01/19/[%E8%BD%AC]%20%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90/

 

 

 

相關文章