•小試牛刀
我們自定義兩個結構體 A 和 B:
struct A { char c1; char c2; int i; double d; }; struct B { char c1; int i; char c2; double d; };通過定義我們可以看出,結構體 A 和 B 擁有相同的成員,只不過在排列順序上有所不同;
眾所周知,char 類型占 1 個字節,int 類型占 4 個字節,double 類型占 8 個字節;
那么,這兩個結構體所占內存空間大小為多少呢?占用的空間是否相同?
空口無憑,讓我們通過編譯器告訴我們答案(我使用的是 VS2022,X86)。
在 main() 函數中輸出如下語句:
int main() { printf("結構體A所占內存大小為:%d\n", sizeof(A)); printf("結構體B所占內存大小為:%d\n", sizeof(B)); return 0; }運行之前,先盲猜一個結果:
sizeof(A) = sizeof(B) = sizeof(c1)+sizeof(c2)+sizeof(i)+sizeof(d) = 1+1+4+8 = 14
到底對不對呢?讓我們來看看運行結果:
amazing~~
竟然一個都沒猜對,這究竟是怎么回事呢?
下面開始進入今天的主題——struct 內存對齊。
•內存對齊
一種提高內存訪問速度的策略,CPU 在訪問未對齊的內存可能需要經過兩次的內存訪問,而經過內存對齊一次就可以了。
假定現在有一個 32 位處理器,那這個處理器一次性讀取或寫入都是四字節。
假設現在有一個 32 位處理器要讀取一個 int 類型的變量,在內存對齊的情況下,處理器是這樣進行讀取的:
那如果數據存儲沒有按照內存對齊的方式進行的話,處理器就會這樣進行讀取:
對比內存對齊和內存沒有對齊兩種情況我們可以明顯地看到,在內存對齊的情況下,取得這個 int型 變量只需要經過一次尋址(0~3);
但在內存沒有對齊的情況下,取得這個 int型 變量需要經過兩次的尋址(0~3 和 4~7),然后再合並數據。
通過上述的分析,我們可以知道內存對齊能夠提升性能,這也是我們要進行內存對齊的原因之一。
•內存對齊的原則
- 對於結構體的各個成員,除了第一個成員的偏移量為 0 外,其余成員的偏移量是 其實際長度 的整數倍,如果不是,則在前一個成員后面補充字節。
- 結構體內所有數據成員各自內存對齊后,結構體本身還要進行一次內存對齊,保證整個結構體占用內存大小是結構體內最大數據成員的最小整數倍。
- 如程序中有 #pragma pack(n) 預編譯指令,則所有成員對齊以 n字節 為准(即偏移量是n的整數倍),不再考慮當前類型以及最大結構體內類型。
下面通過樣例來分享一下我的見解,為方便理解,聲明如下:
- 定義的結構體包含 char , short , int , double類型各一個,並通過不同的組合構造出不同的結構體 Test01 , Test02 , Test03 , Test04
- 內存地址的編號設置為 0~24
- char 類型占1 個 字節,並用橙色填充
- short 類型占 2個 字節,並用黃色填充
- int 類型占 4個 字節,並用綠色填充
- double 類型占 8個 字節,並用藍色填充
- 補充字節用黑色填充
Test01
struct Test01 { char c; short s; int i; double d; }t1;內存分布情況:
- 第一個成員 c 的偏移量為 0,所以成員 c 的內存空間的首地址為 0
- 第二個成員 s 的內存空間的首地址為 2 號地址,偏移量為 2 - 0 = 2
- 第三個成員 i 的內存空間的首地址為 4 號地址,偏移量為 4 - 0 = 4
- 第三個成員 d 的內存空間的首地址為 8 號地址,偏移量為 8 - 0 = 8
- Test01 所占內存大小為 16 個字節
讓我們通過輸出來驗證一下:
void showTest01() { printf("Test01所占內存大小:%d\n",sizeof(Test01)); //並按照聲明順序輸出 Test01 中的成員變量地址對應的十六進制 printf("%p\n", &t1.c); printf("%p\n", &t1.s); printf("%p\n", &t1.i); printf("%p\n", &t1.d); }輸出結果:
我們將輸出的十六進制地址轉化為十進制:
00209400 -> 2135040
00209402 -> 2135042
00209404 -> 2135044
00209408 -> 2135048
- 以第一個成員 c 的起始地址為起點
- 第二個成員 s 的偏移量為 2
- 第三個成員 i 的偏移量為 4
- 第四個成員 d 的偏移量為 8
- 所占內存大小為 16
驗證成功!
Test02
調換一下成員順序,再次測試:
struct Test02 { char c; double d; int i; short s; }t2;內存分布情況:
- 第一個成員 c 的偏移量為 0,所以成員 c 的內存空間的首地址為 0
- 第二個成員 d 的內存空間的首地址為 8 號地址,偏移量為 8 - 0 = 8(double 類型的整倍數)
- 第三個成員 i 的內存空間的首地址為 16 號地址,偏移量為 16 - 0 = 16(int 類型的整倍數)
- 第三個成員 s 的內存空間的首地址為 20 號地址,偏移量為 20 - 0 = 20(short 類型的整倍數)
- Test02 所占內存大小為 24 個字節(結構體占用內存大小是結構體內最大數據成員 double 的最小整數倍:24 / 8 = 4)
接着通過輸出來驗證一下:
我們將輸出的十六進制地址轉化為十進制:
007C9410 -> 8164368
007C9418 -> 8164376
007C9420 -> 8164384
007C9424 -> 8164388
- 以第一個成員 c 的起始地址為起點
- 第二個成員 d 的偏移量為 8164376 - 8164368 = 8
- 第三個成員 i 的偏移量為 8164384 - 8164368 = 16
- 第四個成員 d 的偏移量為 8164388 - 8164368 = 20
- 所占內存大小為 24
驗證成功!
Test03 & Test04
struct Test03 { short s; double d; char c; int i; }t3; struct Test04 { double d; char c; int i; short s; }t4;內存分布情況:
可自行輸出驗證!!!
•總結
通過自行模擬,再回過頭看看內存對齊的原則,是不是有種恍然大明白的感覺~
通過模擬上述不同情況,你會發現同種類型的成員變量通過不同的組合,所占用的總內存是不相同的;
那么,關於結構體內成員定義的順序應該遵循這樣一個原則:按照長度遞增的順序依次定義各個成員。
•聲明
參考資料