C/C++都是按照最大對齊補齊方式的,即按照佔空間最大成員來處理對齊,對齊是由於計算機系統的限制導致的。因爲許多計算機系統對基本數據類型的合法地址做出了一些限制,要求某種類型對象的地址必須是某個值K(通常是2、4或8)的倍數。這種對齊限制簡化了形成處理和內存系統之間接口的硬件設計。
對齊限制可以提高讀寫內存的效率。例如對double類型數據,有了對齊限制,就可以用一個內存操作來讀或者寫值了,否則可能需要執行兩次內存訪問,因爲對象可能被放在兩個8字節內存塊中。
1. 對齊與補齊
對齊原則就是任何K字節的基本對象的地址必須是K的倍數。也就是說一個結構體的double成員大小是8字節,則其地址在對齊原則下就必須是8的倍數。下面是各種基本類型對應的K值。
K | 類型 |
1 | char |
2 | short |
4 | int,float |
8 | long int,double |
當某個數據類型的地址不滿足對齊原則時,編譯器則會在字段的分配中插入間隙,以滿足每個結構元素都滿足它的對齊要求。例如下面的結構:
typedef struct {
int a;
double b;
char c;
int d;
int e;
} A;
因爲a是4字節,而b是8字節的,則爲了讓b滿足對齊要求,編譯器會在a和b中插入4個字節。因爲c是1字節而d是4字節,則編譯會在c和d之間插入3個字節,從而讓d滿足對齊要求。最後結構體A的空間分配如下:
a | 對齊填充 | ||||||
b | |||||||
c | 對齊填充 | d | |||||
e | 對齊填充 | ||||||
PS:最後一行只是爲了表示每行8個字節(去掉的話表格就變形了)。
如果在C或者C++中調用上面的代碼,可以得到結果爲32,sizeof會將e後面補齊的4個字節也算進去。32是4*8,而8就是結構體A中佔空間最大成員b的字節數。所以在計算佔用空間的時候,要優先考慮到最大的成員字節數。
注意,指針在32位操作系統中是佔4字節,而在64位系統中佔8字節,這一點在計算的時候需要考慮。
2. #pragma pack()
對齊限制不是必須的,在C++和C中可以通過宏#pragma pack設置對齊方式。“#pragma pack(K)”是設置對齊方式爲K字節對齊,而“#pragma pack()”則是恢復爲默認的對齊方式。
#include <stdio.h>
#include <stdlib.h>
#pragma pack(1)
typedef struct {
char a;
double b;
char c;
} B;
#pragma pack()
typedef struct {
int a;
double b;
char c;
int d;
int e;
} A;
int main()
{
printf("%d\n", (int)sizeof(A));
printf("%d\n", (int)sizeof(B));
return 0;
}
上面的代碼中,先將結構體B設置爲1字節對齊,然後再將結構體A設置爲默認對齊。最終我們可以得到A結構的大小爲32,而B結構的大小爲10。