C語言之結構體內存的對齊
大綱:
零.引例
一.結構體內存對齊規則
二.怎樣計算結構體的大小
三.設計結構體時要注意的方面
四.為什么存在內存對齊
五.修改默認對齊數
在前面的章節中,我們談到了C語言中整數以及浮點數的儲存
今天,我們來談一談一些關於結構體內存的知識。
零. 引例
我們先來看一個例子:
struct S1 { char c1; int i; char c2; };
大家來猜猜這個結構體S1的內存是多少?
相信會有人給出 6 的結果,他們或許是這樣想的,兩個 char 類型分別為一個字節,一個 int 類型又為4個字節,加起來剛好為6個
但是
結果真是如此嗎?
我們來看看運行結果:
為什么呢,接下來我們就引出正文。
一.結構體內存對齊規則
首先,正如引例所示,結構體的內存並不是簡簡單單的將結構體各個成員的大小相加。
結構體的大小往往遵循着結構體的對齊規則:
- 第一個成員在與結構體變量偏移量為0的地址處。
- 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
- 結構體總大小為最大對齊數(每個成員變量都有一個對齊數)的整數倍。
- 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。
這里要注意的一點就是要解釋一下這個對齊數的概念
對齊數:編譯器默認的一個對齊數 與 該結構體變量成員自身大小的較小值。
注:
不是所有的編譯器都有自己默認的對齊數。
在VS下其默認的對齊數為8
在linux下的默認值就為變量自己的大小,也可以說沒有默認值
二.怎樣計算結構體的大小
在講計算之前,我們繼續來看一看上面的那個例子:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stddef.h> struct S1 { char c1; int i; char c2; }; int main() { printf("該結構體成員 c1 開始的位置為第 %d 個字節\n", offsetof(struct S1, c1)); printf("該結構體成員 i 開始的位置為第 %d 個字節\n", offsetof(struct S1, i)); printf("該結構體成員 c2 開始的位置為第 %d 個字節\n", offsetof(struct S1, c2)); printf("該結構體所占的內存空間為 %d 個字節\n", sizeof(struct S1)); return 0; }
注:
宏 offsetof() 可以計算出結構體各成員所相對開始位置的一個偏移量。
偏移量 : 我們可以理解為把結構體變量第一個成員所儲存的第一個位置置於0,以此遞增
我們來看看結果:
這是為什么呢?
我們來看看上面所提到的結構體內存對齊規則:
然后我們來看示意圖:
此時,關於結構體的大小,我們應該清楚了不少,接下來,我們繼續來看幾道例題:
struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S2)); return 0; }
我們看到,S1與S2的區別僅僅只是調換了一下各成員間的順序,那它所占的內存還是剛才的值嗎:
運行結果:
我們繼續來分析一下:
趁此機會,我們再來鞏固一下:
struct S3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct S3)); return 0; }
它的結果會是多少呢?
不知道大家作對了嗎?
解析:
首先 double 類型占8個字節
char 又展覽接下來的一個
而 int 的對齊數為 4,所以空3個字節從12開始
而這個結構體的最大對齊數為8
所以該結構體占 2*8 = 16個字節
最后,我們再來看一道嵌套結構體的例題:
struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
它的結果又為多少呢?
解析:
我們先來看看規則 4: 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,
結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍
S3的最大對齊數為 8,它的大小為 16 個字節
首先,毋庸置疑的是 char 先放到首位
接下來因為S3的對齊數為 8,所以S3放在了以位置8開始的16個字節
最后是double,對齊數為8,所以放在了24的位置
最后,該結構體的大小為 4*8 = 32 個字節
在進行結構體所占大小的計算中,我們又可以得到一個基本編程常識:
三.設計結構體時要注意的方面
在我們進行結構體的設計中,我們可以把一些所占空間小的,來湊到一起,提高資源的利用率。
正如上文所提到的例1與例2,結構體成員完全相同,但順序不同,兩個結構體的大小也截然不同
//例1 struct S1 { char c1; int i; char c2; }; //例2 struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//8 return 0; }
四.為什么存在內存對齊
對於這個原因,目前話沒有一種完全正確的答案,但是:
大部分的參考資料都是如是說的:
1. 平台原因(移植原因):
不是所有的硬件平台都能訪問任意地址上的任意數據的;
某些硬件平台只能在某些地址 處取某些特定類型的數據,否則拋出硬件異常。
2. 性能原因:
數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。
原因在於,為了訪問未對齊的內存,處理器 需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
對於原因而我們來看一個示意圖:
五.修改默認對齊數
我們通常使用如下的預處理命令來修改編譯器的默認對齊數:
#pragma pack()
如果()里面不加數字,則默認為編譯器的默認對齊數
我們修改的時候,只需在()里加一個數字就行
取消的時候再添加一次 #pragma pack() 即可
注:
再()里添加的數字,我們通常加的都是2的多少次方
下面來舉一個實例:
#include <stdio.h> #pragma pack(8)//設置默認對齊數為8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消設置的默認對齊數,還原為默認 #pragma pack(1)//設置默認對齊數為8 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消設置的默認對齊數,還原為默認
int main() { //輸出的結果是什么? printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//6 return 0; }
由此可見,我們也可以通過修改默認對齊數來節約 結構體使用的空間。
關於結構體內存的講解便到此為止。
因筆者水平有限,若有錯誤之處,還望多多指正。
若有想回憶 C語言中整數以及浮點數是怎樣儲存的讀者,可以參見 :C語言之數據在內存中的存儲