结构体内存对齐(如何计算结构体的大小)
文章目錄
- 結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
- 結(jié)構(gòu)體大小計(jì)算 - 三步曲
- 為什么存在內(nèi)存對(duì)齊?
- 設(shè)計(jì)結(jié)構(gòu)體時(shí)的技巧
- 修改默認(rèn)對(duì)齊數(shù)
結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
我們知道,整型變量有自己的大小,浮點(diǎn)型變量有自己的大小,數(shù)組也有自己的大小,那么結(jié)構(gòu)體有沒(méi)有自己的大小呢?
回答是肯定的,結(jié)構(gòu)體也有自己的大小,但是結(jié)構(gòu)體的大小并不是簡(jiǎn)單地將每個(gè)結(jié)構(gòu)體成員的大小相加就能得到。
結(jié)構(gòu)體的大小計(jì)算遵循結(jié)構(gòu)體的對(duì)齊規(guī)則:
對(duì)齊數(shù) = 該結(jié)構(gòu)體成員變量自身的大小與編譯器默認(rèn)的一個(gè)對(duì)齊數(shù)的較小值。
注:VS中的默認(rèn)對(duì)齊數(shù)為8,不是所有編譯器都有默認(rèn)對(duì)齊數(shù),當(dāng)編譯器沒(méi)有默認(rèn)對(duì)齊數(shù)的時(shí)候,成員變量的大小就是該成員的對(duì)齊數(shù)。
結(jié)構(gòu)體大小計(jì)算 - 三步曲
知道了結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則,我們就可以計(jì)算結(jié)構(gòu)體的大小了。計(jì)算結(jié)構(gòu)體的大小可分為三個(gè)步驟。我們拿下面這個(gè)結(jié)構(gòu)體舉例:
struct S {double d;char c;int i; };第一步:找出每個(gè)成員變量的大小將其與編譯器的默認(rèn)對(duì)齊數(shù)相比較,取其較小值為該成員變量的對(duì)齊數(shù)。
注:我使用的是VS編譯器,故默認(rèn)對(duì)齊數(shù)為8。
第二步:根據(jù)每個(gè)成員對(duì)應(yīng)的對(duì)齊數(shù)畫(huà)出它們?cè)趦?nèi)存中的相對(duì)位置。
第三步:通過(guò)最大對(duì)齊數(shù)決定最終該結(jié)構(gòu)體的大小。
通過(guò)圖我們可以知道,綠色部分(double d成員占用)+紅色部分(char c成員占用)+紫色部分(int i成員占用)+紅色與紫色之間的白色部分(浪費(fèi)掉了)總共占用了16個(gè)字節(jié)的內(nèi)存空間。
我們需要將它們總共占用的內(nèi)存空間(16)與結(jié)構(gòu)體成員的最大對(duì)齊數(shù)(8)相比較,結(jié)構(gòu)體的總大小為最大對(duì)齊數(shù)的整數(shù)倍,此時(shí)16正好是8的整數(shù)倍,所以該結(jié)構(gòu)體在VS編譯器下的大小就16個(gè)字節(jié)。即創(chuàng)建一個(gè)該類(lèi)型的結(jié)構(gòu)體變量,內(nèi)存需為其開(kāi)辟16個(gè)字節(jié)的內(nèi)存空間。
注意:大多數(shù)情況下,成員變量已經(jīng)占用的總字節(jié)個(gè)數(shù)并不一定正好為其成員變量中的最大對(duì)齊數(shù)的整數(shù)倍,這時(shí)我們需要將其擴(kuò)大為最大對(duì)齊數(shù)的整數(shù)倍。
為什么存在內(nèi)存對(duì)齊?
平臺(tái)原因(移植原因): 不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些平臺(tái)只能在某些地址處取得某些特定類(lèi)型的數(shù)據(jù),否則拋出硬件異常。
比如,當(dāng)一個(gè)平臺(tái)要取一個(gè)整型數(shù)據(jù)時(shí)只能在地址為4的倍數(shù)的位置取得,那么這時(shí)就需要內(nèi)存對(duì)齊,否則無(wú)法訪問(wèn)到該整型數(shù)據(jù)。
性能原因: 數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能的在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需一次。
在畫(huà)圖時(shí)可能有博友會(huì)想,內(nèi)存這么重要,在進(jìn)行內(nèi)存對(duì)齊的時(shí)候怎么還有內(nèi)存被白白浪費(fèi)掉呢?
現(xiàn)在看來(lái),其實(shí)結(jié)構(gòu)體的內(nèi)存對(duì)齊是拿空間來(lái)?yè)Q取時(shí)間的做法。
設(shè)計(jì)結(jié)構(gòu)體時(shí)的技巧
其實(shí)在我們?cè)O(shè)計(jì)結(jié)構(gòu)體的時(shí)候,如果結(jié)構(gòu)體成員的順序設(shè)計(jì)得合理的話,是可以避免不必要的內(nèi)存消耗的。
兩個(gè)結(jié)構(gòu)體的成員變量相同,但是成員變量的順序不同,可能就會(huì)出現(xiàn)結(jié)構(gòu)體的大小不同的情況:
我們可以看到,結(jié)構(gòu)體1和結(jié)構(gòu)體2的成員變量一模一樣,可是當(dāng)我們按照內(nèi)存對(duì)齊規(guī)則來(lái)計(jì)算兩個(gè)結(jié)構(gòu)體的大小的時(shí)候,會(huì)發(fā)現(xiàn)兩個(gè)結(jié)構(gòu)體的大小不一樣,在VS編譯器下第一個(gè)結(jié)構(gòu)體大小為8,第二個(gè)結(jié)構(gòu)體大小為12。
可以見(jiàn)得,結(jié)構(gòu)體成員變量的順序不同,可能會(huì)造成內(nèi)存不必要的損失。將占用空間小的成員盡量集中在一起,可以有效地避免內(nèi)存不必要的浪費(fèi)。
修改默認(rèn)對(duì)齊數(shù)
要修改編譯器的默認(rèn)對(duì)齊數(shù),我們需要借助于以下預(yù)處理命令:
#pragma pack()如果在該預(yù)處理命令的括號(hào)內(nèi)填上數(shù)字,那么默認(rèn)對(duì)齊數(shù)將會(huì)被改為對(duì)應(yīng)數(shù)字;如果只使用該預(yù)處理命令,不在括號(hào)內(nèi)填寫(xiě)數(shù)字,那么會(huì)恢復(fù)為編譯器默認(rèn)的對(duì)齊數(shù)。
#include <stdio.h>#pragma pack(4)//設(shè)置默認(rèn)對(duì)齊數(shù)為4 struct S1 {char a;//1/4->1int b;//4/4->4char c;//1/4->1 };//12 #pragma pack()//取消設(shè)置的默認(rèn)對(duì)齊數(shù),還原為默認(rèn)#pragma pack(1)//設(shè)置默認(rèn)對(duì)齊數(shù)為1 struct S2 {char a;//1/1->1int b;//4/1->1char c;//1/1->1 };//6 #pragma pack()//取消設(shè)置的默認(rèn)對(duì)齊數(shù),還原為默認(rèn)int main() {printf("%d\n", sizeof(struct S1));//打印結(jié)果為12printf("%d\n", sizeof(struct S2));//打印結(jié)果為6return 0; }于是,當(dāng)結(jié)構(gòu)體的對(duì)齊方式不合適的時(shí)候,我們可以自己更改默認(rèn)對(duì)齊數(shù)。
總結(jié)
以上是生活随笔為你收集整理的结构体内存对齐(如何计算结构体的大小)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。