C/C++內存對齊詳解-轉載

什麼是內存對齊

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

C/C++內存對齊詳解-轉載

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

為什麼要進行內存對齊

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

現在考慮4字節存取粒度的處理器取int類型變量(32位系統),該處理器只能從地址為4的倍數的內存開始讀取數據。

假如沒有內存對齊機制,數據可以任意存放,現在一個int變量存放在從地址1開始的聯繫四個字節地址中,該處理器去取數據時,要先從0地址開始讀取第一個4字節塊,剔除不想要的字節(0地址),然後從地址4開始讀取下一個4字節塊,同樣剔除不要的數據(5,6,7地址),最後留下的兩塊數據合併放入寄存器.這需要做很多工作.


C/C++內存對齊詳解-轉載


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


C/C++內存對齊詳解-轉載

內存對齊規則

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

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

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

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

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

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

C/C++內存對齊詳解-轉載

以上測試都是在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++內存對齊詳解-轉載

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


分享到:


相關文章: