天天看點

【轉載知乎】C/C++記憶體對齊詳解

原文連接配接:https://zhuanlan.zhihu.com/p/30007037

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位址),最後留下的兩塊資料合并放入寄存器.這需要做很多工作.

【轉載知乎】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/

更多文章見本文公衆号:碼農有道