徹底搞清計算結構體大小和數據對齊原則
By Qianghaohao
數據對齊:
許多計算機系統對基本數據類型合法地址做出了一些限制,要求某種類型對象的地址必須是
某個值K(通常是2,4或8)的倍數。這種對齊限制簡化了形成處理器和存儲器系統之間的接口的硬件
設計。例如,假設一個處理器總是從存儲器中取出8個字節,則地址必須為8的倍數。如果我們能保
證將所有的double類型數據的地址對齊成8的倍數,那么就可以用一個存儲器操作來讀或者寫值了。
否則,我們可能需要執行兩次存儲器訪問,因為對象可能被分放在兩個8字節存儲塊中。
當數據類型為結構體時,編譯器可能需要在結構體字段的分配中插入間隙,以保證每個結構元素都
滿足它的對齊要求。而結構本身對它的地址也有一些對齊要求,此時可能需要在結構末尾填充一些
空間,以滿足結構體整體的對齊----向結構體元素中最大的元素對齊。稍后會用代碼說明!!!
Linux和Microsoft Windows的對齊方式有何不同:
一.Linux的對齊策略:
在Linux中2字節數據類型(例如short)的地址必須是2的倍數,而較大的數據類型(例如int,int *
,float和double)的地址必須是4的倍數。也就是說Linux下要么2字節對齊,要么4字節對齊,沒
有其他格式的對齊。
二.Microsoft Windows的對齊策略:
在Windows中對齊要求更嚴--任何K字節基本對象的地址都必須是K的倍數,K=2,4,或者8.
特別地,double或者long long類型數據的地址應該是8的倍數。可以看出Windows的對齊策略和
Linux還是不同的。稍后用代碼說明!!!
接下來用代碼和圖文說明兩者的對齊方式(不同的對齊方式產生的結構體大小不同):
測試代碼如下:
///////////////////////////////////// // filename:DataAlignment ///////////////////////////////////// #include<stdio.h> typedef struct { char c; int i[2]; double v; }S; int main() { printf("size of S = %d\n", sizeof(S)); return 0; }程序中定義了一個結構體,在沒有任何數據對齊時內存布局如下:

一.在紅帽Linux i686上編譯編譯后結構S的布局如下:

由於要保證結構體每個元素都要數據對齊,因此必須在c和i[0]之間插入3字節的間隙(圖中陰影部分為編譯器插入的間隙)
使得i[0]和后面的元素的的偏移量都為4的倍數,這樣最終S結構大小為20字節。
運行程序結果為:
size of S = 20
二.在Microsoft Windows 上編譯后S的內存布局如下:

在windows下int類型4個字節,因此int類型要向4字節對齊,double類型8字節,因此要向8字節對齊。因此在
c和i[0]之間插入3字節的間隙(圖中陰影部分),使得i[0]的偏移量為4的倍數,同時在i[1]和v之間插入4字節的間隙,
使得v的偏移量為8的倍數。這樣最終S結構的大小為24字節。
運行程序結果為:
size of S = 24
從以上對比可以看出Linux下和windows下的對齊策略是不同的,這就導致在兩個平台下結構體的大小不同。
現在考慮如下代碼:
#include<stdio.h> typedef struct { int i; int j; char c; }S1; int main() { printf("size of S1 = %d\n", sizeof(S1)); return 0; }
可能很多人認為編譯后運行結果為9,以為結構體的每個元素都滿足各自的對齊要求。其實不然,別忘了還有要考慮
結構體整體的對齊。假如有如下聲明:
S1 d[4];
如果這樣分配9個字節,不可能滿足d的每個元素的對齊要求,因為這些元素的地址分別為xd,xd+9,xd+18,
xd+27。這樣只有第一個元素滿足4字節對齊的要求,而其他的元素的地址都不是4的倍數。
因此編譯器會在結構體的末尾填充3字節滿足結構體整體的對齊要求,填充后的內存布局如下:

這樣一來,d的元素的地址分別為xd,xd+12,xd+24,xd+36,滿足4字節的整數倍,這樣最終S1的大小
為12字節,而不是9字節.
總結:通過代碼的對比,可以看出Linx和Windwos的數據對齊有所差異,這就導致
在一些情況下兩者平台結構體類型大小的不同。通過以上示例分析我們可以很簡單的
計算出在任何平台下結構體的大小。讀者可以找相關的練習題繼續練習下,驗證結果
的正確性。相信仔細閱讀本文應該能搞定所有類似的問題!!!
本文參考書籍:
鏈接:
深入理解計算機系統原書第2版