C语言 计算结构体大小
本文主要參考:結構體內存對齊(如何計算結構體的大小)
前言
數組是相同類型的元素的集合,只要會計算單個元素的大小,整個數組所占空間等于基礎元素大小乘上元素的個數。
結構體中的成員可以是不同的數據類型,成員按照定義時的順序依次存儲在連續的內存空間。和數組不一樣的是,結構體的大小不是所有成員大小簡單的相加,需要考慮到系統在存儲結構體變量時的地址對齊問題。
如何計算結構體大小
以下面這個結構體為例:
struct S {double d;char c;int i; };使用 sizeof(struct S) 計算其大小發現結果是16,并不是double 8字節+char 1字節+int 4字節=13字節。
下面分3步分析該結構體的大小:
第一步:找出每個成員變量的大小將其與編譯器的默認對齊數相比較,取其較小值為該成員變量的對齊數。
【注:使用VS編譯運行,故默認對齊數為8。】
第二步:根據每個成員對應的對齊數畫出它們在內存中的相對位置。
第三步:通過最大對齊數決定最終該結構體的大小。
通過圖我們可以知道,綠色部分(double d成員占用)+紅色部分(char c成員占用)+紫色部分(int i成員占用)+紅色與紫色之間的白色部分(浪費掉了)總共占用了16個字節的內存空間。
我們需要將它們總共占用的內存空間(16)與結構體成員的最大對齊數(8)相比較,結構體的總大小為最大對齊數的整數倍,此時16正好是8的整數倍,所以該結構體在VS編譯器下的大小就16個字節。即創建一個該類型的結構體變量,內存需為其開辟16個字節的內存空間。
注意:大多數情況下,成員變量已經占用的總字節個數并不一定正好為其成員變量中的最大對齊數的整數倍,這時我們需要將其擴大為最大對齊數的整數倍。
代碼驗證上述分析:
#include <stdio.h>struct S {double d;char c;int i; };int main(void) {struct S test;printf("%d\n",sizeof(struct S));printf("%p\n",&test);printf("%p\n", &test.d);printf("%p\n", &test.c);printf("%p\n", &test.i);return 0; }結果:
由該驗證結果可見:
1)該結構體一共16個字節;
2)第一個成員d是從結構體的初始地址開始存儲的;
3)第二個成員c相對第一個成員的地址偏移了8字節;
4)第三個成員i相對第二個成員的地址偏移了4字節。
因此是符合上面的圖表所示的。
結構體大小占用規則
1、第一個成員在與結構體變量偏移量為0的地址處。(即結構體的首地址處,即對齊到0處)
2、其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
3、結構體的總大小為最大對齊數(每個成員變量都有一個對齊數)的整數倍。
4、如果嵌套了結構體,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。
對齊數 = 該結構體成員變量自身的大小與編譯器默認的一個對齊數的較小值。
注:VS中的默認對齊數為8,不是所有編譯器都有默認對齊數,當編譯器沒有默認對齊數的時候,成員變量的大小就是該成員的對齊數。
計算舉例
1、簡單結構體
struct s1{char ch1;//1char ch2;//1int i;//空2 + 4 = 6 };這個結構體的大小容易計算,為8,那么下面這個呢
struct s2{char ch1;//1(字節)int i; //空3 + 4 = 7char ch2;//1 + 空3 };這個結構體大小是12字節,為什么呢?
其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。因此第二個成員的對齊數是4,相對首地址偏移4字節,與第一個成員之間空了3個字節,第三個成員的對齊數是1,相對首地址偏移5字節(緊隨第二個成員)。
結構體的總大小為最大對齊數(每個成員變量都有一個對齊數)的整數倍。最大對齊數是4,如果只是1+空3+4+1=9還不夠最大對齊數,因此后面還需補齊3個空字節。所以總大小為12字節。
2、成員包含數組的結構體
struct s3{char ch;//1(字節)int i; //空3 + 4 = 7(字節)char str[10];//10 + 空2 = 12 };這個結構體的大小是20,先看前兩個成員,大小是8,這個char類型的數組,只需要把它看做十個char型連在一起即可,加起來就是18,再滿足結構體總大小為最大對齊數的整數倍,最大對齊數是第二個成員對應的4,數組應該拆分來看,不能當整體看,所以總大小就是20。其中包含5個空字節。
3、成員包含結構體的結構體
struct s4{char ch;//1int i;//空3 + 4 = 7struct s{char ch1; //1int j; //空3 + 4 = 7};float f;//4 };里面這個結構體的大小是8,那么是否結構體大小就要向8對齊呢?這個結構體的大小是20,很明顯不是8的倍數。所以計算結構體大小時是把里面這個結構體就看做是一個char,和一個int,不是看做一個整體。
擴展:
上面這種寫法,中間嵌套的結構體沒有變量名,訪問方式里面的成員如下:
可以改成:
struct s4{char ch;//1int i;//空3 + 4 = 7struct s{char ch1; //1int j; //空3 + 4 = 7}S;float f;//4 };中間嵌套的結構體有變量名了,訪問方式里面的成員如下:
struct s4 test; test.S.ch1 = 0; test.S.j = 0;但上面兩種寫法的結構體總大小都是20。
4、成員包含聯合體的結構體
struct s5{char ch;//1int i;//空3+4=7union{char ch1;int j;//4}; };聯合體大小就是成員中最大類型的大小,所以這個結構體大小是12.
修改默認對齊數
要修改編譯器的默認對齊數,我們需要借助于以下預處理命令:
#pragma pack()如果在該預處理命令的括號內填上數字,那么默認對齊數將會被改為對應數字;如果只使用該預處理命令,不在括號內填寫數字,那么會恢復為編譯器默認的對齊數。
#include <stdio.h>#pragma pack(4)//設置默認對齊數為4 struct S1 {char a;//1/4->1int b;//4/4->4char c;//1/4->1 };//12 #pragma pack()//取消設置的默認對齊數,還原為默認#pragma pack(1)//設置默認對齊數為1 struct S2 {char a;//1/1->1int b;//4/1->1char c;//1/1->1 };//6 #pragma pack()//取消設置的默認對齊數,還原為默認int main() {printf("%d\n", sizeof(struct S1));//打印結果為12printf("%d\n", sizeof(struct S2));//打印結果為6return 0; }【關于#pragma pack()的作用域】
#pragma pack(n)是#pragma編譯預處理指令最基本的用法,其作用是改變編譯器的對齊方式,n值可以取(1, 2, 4, 8, 16) 中任意一值。
若存在多個#pragma pack (n),遵從向上對齊原則,即某個結構體定義上方最近的一個#pragma pack()
#pragma pack()下方所有的代碼,對齊原則都變更成設置的值。
如果放在頭文件里,所有包含了該頭文件的C文件都按此設置的對齊值編譯執行。
https://www.jianshu.com/p/90a6eef329ec
思考:為什么存在內存對齊?
平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些平臺只能在某些地址處取得某些特定類型的數據,否則拋出硬件異常。
比如,當一個平臺要取一個整型數據時只能在地址為4的倍數的位置取得,那么這時就需要內存對齊,否則無法訪問到該整型數據。
性能原因: 數據結構(尤其是棧)應該盡可能的在自然邊界上對齊。原因在于,為了訪問未對齊內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需一次。因此在某些情況下需要在成員間或是末尾補齊空字節,以空間換時間。
設計結構體時的技巧
其實在我們設計結構體的時候,如果結構體成員的順序設計得合理的話,是可以避免不必要的內存消耗的。
兩個結構體的成員變量相同,但是成員變量的順序不同,可能就會出現結構體的大小不同的情況:
我們可以看到,結構體1和結構體2的成員變量一模一樣,可是當我們按照內存對齊規則來計算兩個結構體的大小的時候,會發現兩個結構體的大小不一樣,在VS編譯器下第一個結構體大小為8,第二個結構體大小為12。
可以見得,結構體成員變量的順序不同,可能會造成內存不必要的損失。將占用空間小的成員盡量集中在一起,可以有效地避免內存不必要的浪費。
結語
在其他文章中看過對結構體大小計算規則的一些概括,都是不太嚴謹或是有錯誤的;
比如:
1,每個結構體成員的起始地址為該成員大小的整數倍,即int型成員的起始地址只能為0、4、8等
2,結構體的大小為其中最大成員大小的整數倍
反例:
struct s3{char ch;//1(字節)int i; //空3 + 4 = 7(字節)char str[11];//11 + 空1 = 12 };這個結構體的大小是20,第2規則 結構體的大小為其中最大成員大小的整數倍,這里面最大成員是str數組為11個字節,20并不是11的整數倍。應該理解成:結構體的總大小為最大對齊數的整數倍。最大對齊數是第二個成員對應的4。
又比如:
一、結構體成員的偏移量必須是成員大小的整數倍(0被認為是任何數的整數倍)
二、結構體大小必須是所有成員大小的整數倍
以上都沒有考慮修改默認對齊數的情況:
#pragma pack(1)//設置默認對齊數為1 struct S2 {char a;//1/1->1int b;//4/1->1char c;//1/1->1 };//6 #pragma pack()//取消設置的默認對齊數,還原為默認該結構體大小為6,顯然不是第二個成員大小4的整數倍。
參考鳴謝:
結構體內存對齊(如何計算結構體的大小)
sizeof淺析(一)——求結構體大小
結構體的大小如何計算
實例講解c語言結構體大小 sizeof(struct A)
第10章結構體01——結構體字節大小的計算
總結
以上是生活随笔為你收集整理的C语言 计算结构体大小的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言浮点数据在内存中的存储方式
- 下一篇: Source Insight 使用教程(