C/C++內存對齊詳解


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 必須內存對齊,否則指針訪問會出錯)

 

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

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個字節。

根據上面的分析,不難得出上面例子三個結構體的內存布局如下:

#pragma pack(n)

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

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

如果前面加上#pragma pack(2),有效對齊值為2字節,此時根據對齊規則,三個結構體的大小應為6,8,6。內存分布圖如下:

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

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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM