關於結構體內存對齊的問題,最直觀的體現便是在計算結構體大小的問題上。
我們來看一個例子:
#include<stdio.h>
#include<stdlib.h>
int main()
{
struct s
{
char a;
int b;
double c;
}s;
printf("%d\n", sizeof(s));
system("pause");
return 0;
}
那麼在沒有考慮到結構體內存對齊之前,我們的常規思路應該是直接計算每個類型的大小,即1+4+8=13。但是我們看輸出結果:
結果並不是13,而是16。爲什麼?
那麼這就是爲什麼要有結構體內存對齊的原因了,我們先通過畫圖來剖析一下上述題目的結構體內存對齊產生的原因與過程。
我們在內存訪問尋址時不能從任意處開始,所以我們在圖裏定義一些確定的訪問位置。
如圖,在無結構體內存對齊的情況下(也就是按1+4+8=13計算),cpu讀取a的時候可以直接從內存裏讀取。而b佔4個字節所以緊挨着a往下放,那麼cpu第一次訪問內存時要先把b在前面的3個字節讀取出來,然後第二次訪問時再把b的剩下的一個字節讀取出來。那麼cpu光把b變量讀取就要訪問內存兩次,如果再內存中充滿了巨大的變量數據,cpu就要頻繁的訪問內存,那麼讀取數據的性能就會大大下降,很大程度上耗費了時間。
那麼內存對齊出現的原因總結爲如下兩點:
- 平臺原因(移植原因):不是所有硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
- 性能原因:數據結構(尤其是棧),應該儘可能地在自然邊界上對齊。原因在於,爲了未訪問對齊的內存,處理器需要作兩次內存訪問;而對齊的內存僅需訪問一次。
總的來說:結構體內存對齊的出現就是爲了拿空間來換取時間
經過閱讀一些書籍,我對結構體的內存對齊規則總結如下:
- 第一個成員在與結構體變量偏移量爲0的地址處。如上述例子的變量a,就是從偏移量爲0處開始存儲。
- 其他成員要對齊到某個數字(對齊數)的整數倍的地址處。劃重點:對齊是指,除了第一個變量,剩下的任何變量放入時的起始偏移量能整除對齊數。若不對齊,則讓起始偏移量增加至最小能整除對齊數的數。
- 任何編譯器都沒有默認對齊數。
- 每一個成員都有一個對齊數(一般爲該成員自身的類型大小)。
- 結構體大小爲最大對齊數的整數倍。
- 如果有嵌套結構體的情況,嵌套結構體對齊到自己最大對齊數的整數倍處,結構體的整體大小就是所有所有最大對齊數(含嵌套進來的結構體的對齊數)的整數倍。
那麼清楚了規則之後我們再來看這個題目就很明朗了。
如圖:變量a佔一個字節,所以可以被cpu直接讀取寫入,b佔4個字節,所以b從起始偏移量爲4的地方開始存儲,這樣在cpu讀取b只需訪問內存一次。
通常在設計結構體的時候,我們既要滿足對齊,又要節省空間,所以我們要讓佔用空間小的成員儘量集中在一起。
例如:
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
在上面這段代碼裏,s1和s2類型的成員一模一樣,但是s1和s2所佔空間大小有了一定的區別。