C語言結(jié)構(gòu)體字節(jié)對(duì)齊的實(shí)現(xiàn)深入分析
前言
本教程可能會(huì)用到一點(diǎn)匯編的知識(shí),看不懂沒關(guān)系,知道是那個(gè)意思就行了。使用的工具是vs2010。
一、什么是字節(jié)對(duì)齊
字節(jié)對(duì)齊是字節(jié)按照一定規(guī)則在空間上排列。
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對(duì)任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定類型變量的時(shí)候經(jīng)常在特 定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。
在我們之前寫程序的時(shí)候可能會(huì)發(fā)現(xiàn)有的時(shí)候你定義的變量是一個(gè)字節(jié),但是他在內(nèi)存中依然按照四個(gè)字節(jié)給你存儲(chǔ),因?yàn)樵?2為系統(tǒng)中,4字節(jié)對(duì)齊執(zhí)行效率是最快的。這就是一種犧牲內(nèi)存換取性能的方案。
舉個(gè)栗子
我們知道,全局變量在在內(nèi)存中是有一個(gè)固定的地址的,如果不重新編譯的話,這個(gè)地址是不會(huì)改變的。所以我們拿全局變量舉例。
如下,我們定義兩個(gè)全局變量:
一個(gè)是char(單字節(jié))的一個(gè)是int(4字節(jié)的)
然后我們?cè)趍ain函數(shù)中給他們賦值,如下:
然后斷點(diǎn)、F7編譯、F5調(diào)試、ALT+8轉(zhuǎn)到反匯編:
可以看到他們之間差值為4字節(jié)
再次測(cè)試,如下:
反匯編:
差值為2。
因?yàn)樽止?jié)對(duì)齊的原因,int類型的起始地址必須是4的整數(shù)倍,short類型的起始地址也必須是2的倍數(shù),當(dāng)然char就沒有那么約束了,因?yàn)樗皇且粋€(gè)字節(jié)的。并不是說char類型的變量與int變量的差值是因?yàn)閕nt類型占了四個(gè)字節(jié)所以差值才為4 。
再做測(cè)試,如下:
打亂順序。查看反匯編:
總結(jié)如圖。
再次強(qiáng)掉、因?yàn)樽止?jié)對(duì)齊的原因,變量的起始地址必須是變量字節(jié)大小的整數(shù)倍
二、結(jié)構(gòu)體字節(jié)對(duì)齊
結(jié)構(gòu)體中成員的存儲(chǔ)方式也是按照上面的字節(jié)對(duì)齊方式進(jìn)行存儲(chǔ)的。不過有一點(diǎn)區(qū)別:結(jié)構(gòu)體的起始地址是其最寬數(shù)據(jù)類型的整數(shù)倍(千萬不要死記:結(jié)構(gòu)體寬度就是最寬數(shù)據(jù)類型的整數(shù)倍,因?yàn)檫@樣容易忘,下面我來和大家分析為什么是這樣,知道為什么才能記得更久)。
舉個(gè)栗子
結(jié)果:
我們分析一下:
int 4字節(jié)、char 1字節(jié)、double 8字節(jié),按理說應(yīng)該是13字節(jié)對(duì)吧。
分析如下:
int類型占了4個(gè)字節(jié),這個(gè)時(shí)候char類型因?yàn)樽止?jié)對(duì)齊的原因、他的起始地址是1的整數(shù)倍,所以它可以挨著int類型。但是double,他是8個(gè)字節(jié)的,由于字節(jié)對(duì)齊的原因、他的起始地址必須是8的倍數(shù),所以他和char類型之間會(huì)差3。但是int、char、double他們是一個(gè)結(jié)構(gòu)體的成員,不是分開存儲(chǔ)的。所以char和double之間空的三個(gè)字節(jié)因?yàn)樽止?jié)對(duì)齊的原因必須補(bǔ)齊。
再來:
可以先猜測(cè)一下結(jié)果。
結(jié)果如下:
這里大家可以試著推測(cè),只需要記住因?yàn)樽止?jié)對(duì)齊的原因,char起始地址必須是1的整數(shù)倍、int必須是4的整數(shù)倍等等。
三、#pragma pack()的使用
當(dāng)對(duì)內(nèi)存要求較高的時(shí)候,我們不得不拋棄性能。這個(gè)時(shí)候可以通過#pragma pack(n)來強(qiáng)制結(jié)構(gòu)體成員的對(duì)齊方式
#pragma pack(1)
struct st_info
{
char a;
int b;
};
#pragma pack()
<1> #pragma pack(n)中的n用來設(shè)定變量以n字節(jié)的方式對(duì)齊,可以設(shè)定的值包括:1、2、4、8,VC編譯器默認(rèn)是8。
<2> 若需要強(qiáng)制取消字節(jié)對(duì)齊方式,則可使用#pragma pak()取消。
舉個(gè)栗子:
示例代碼:
#include <stdio.h> #include <Windows.h> #pragma pack(2) struct stinfo { char t; int x; char y; double m; }; #pragma pack() int main() { printf("%d\n", sizeof(stinfo)); while(1); return 0; }
如下:
這里我們讓結(jié)構(gòu)體以2字節(jié)的方式對(duì)齊,所以猜想:
char t因?yàn)樽止?jié)對(duì)齊的原因起始地址應(yīng)該是2的整數(shù)倍,但是他是首地址,不用管。
t = 1個(gè)字節(jié)。
int x類型因?yàn)閜ack(2)的原因,起始地址應(yīng)該是2的整數(shù)倍,所以它和chart的差值應(yīng)該為2,也就是補(bǔ)1個(gè)字節(jié)。
t+1+x = 6個(gè)字節(jié)
到這里,t和x已經(jīng)占有6個(gè)字節(jié)。
char y因?yàn)閜ack(2)的原因,他的起始地址應(yīng)該是2的整數(shù)倍,但是上面六個(gè)字節(jié)正好是2的倍數(shù)。
t+1+x+y = 7個(gè)字節(jié)
到這里t、x、y三個(gè)成員已經(jīng)占有7個(gè)字節(jié)。
double m因?yàn)閜ack(2)的原因,起始地址不得不是2的整數(shù)倍,但是上面7個(gè)字節(jié)顯然不對(duì),所以因?yàn)樽止?jié)對(duì)齊的原因,上面t、x、y三個(gè)成員需要再補(bǔ)一個(gè)字節(jié),這個(gè)時(shí)候加上double的8個(gè)字節(jié)。
t+1+x+y+1+8 = 16個(gè)字節(jié)
結(jié)果如下:
猜想正確。
如果我們不強(qiáng)制字節(jié)對(duì)齊的話:
char t一個(gè)字節(jié)
int x起始地址必須是4的整數(shù)倍,所以t必須補(bǔ)3個(gè)字節(jié)
x+t+3 = 8個(gè)字節(jié)
char y一個(gè)字節(jié)
x+t+3+y = 9個(gè)字節(jié)
double m起始地址必須是8的倍數(shù),所以x+t+y需要補(bǔ)7個(gè)字節(jié)。
x+t+3+y+7+8 = 24個(gè)字節(jié)
結(jié)果如下:
對(duì)于pack(1)、pack(4)、pack(8)大家自己嘗試下吧。
總結(jié)
通過上面的講解,想必大家應(yīng)該知道結(jié)構(gòu)體的字節(jié)數(shù)為什么是成員中最寬數(shù)據(jù)類型字節(jié)數(shù)的整數(shù)倍了的,但是還是那句話,不要死記這一點(diǎn),明白為什么才是重點(diǎn)、才能記得更久。
因?yàn)樽止?jié)對(duì)齊的原因、結(jié)構(gòu)體中成員的起始地址、假設(shè)該成員是int類型,那么它的起始地址必須是4的整數(shù)倍,如果是double類型,那么他的起始地址必須是8的整數(shù)倍。當(dāng)然也可以強(qiáng)制他們的起始地址是一個(gè)固定數(shù)字(1、2、4、8)的整數(shù)倍。通過#pragma pack(n)即可。
到此這篇關(guān)于C語言結(jié)構(gòu)體字節(jié)對(duì)齊的實(shí)現(xiàn)深入分析的文章就介紹到這了,更多相關(guān)C語言結(jié)構(gòu)體字節(jié)對(duì)齊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++賦值函數(shù)+移動(dòng)賦值函數(shù)+移動(dòng)構(gòu)造函數(shù)詳解
這篇文章主要介紹了C++賦值函數(shù)+移動(dòng)賦值函數(shù)+移動(dòng)構(gòu)造函數(shù)詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08C++類和對(duì)象深入探索之分文件編寫點(diǎn)和圓的關(guān)系詳解
先前把C++類和對(duì)象的封裝講完了,并且留下了一個(gè)判斷兩個(gè)立方體是否相等的案例,但是那么多知識(shí)點(diǎn),僅僅一個(gè)案例是不夠的,所以再來一個(gè)分文件編寫點(diǎn)圓關(guān)系的案例;創(chuàng)建圓類和點(diǎn)類,圓類包含點(diǎn)類,算是一個(gè)嵌套吧,順便復(fù)習(xí)一下分文件編寫的方法,開整2022-05-05C語言實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(多文件)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12C++實(shí)現(xiàn)四則混合運(yùn)算計(jì)算器
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)四則混合運(yùn)算計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11C語言數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)
鏈表是一種物理存儲(chǔ)結(jié)構(gòu)上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的。本文將用C語言實(shí)現(xiàn)單鏈表,需要的可以參考一下2022-06-06Qt如何實(shí)現(xiàn)輸入框@聯(lián)系人的@檢測(cè)的示例
本文主要介紹了Qt如何實(shí)現(xiàn)輸入框@聯(lián)系人的@檢測(cè)的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08c++連續(xù)輸入未知個(gè)數(shù)的數(shù)字操作
這篇文章主要介紹了c++連續(xù)輸入未知個(gè)數(shù)的數(shù)字操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12