字节对齐和C/C++函数调用方式学习总结(多篇节选)
字節(jié)對齊和C/C++函數(shù)調(diào)用方式學(xué)習(xí)總結(jié)
created: 04-06-17
last saved:
author: ayixidelu
前言:
《***軟件編程規(guī)范》中提到:“在定義結(jié)構(gòu)數(shù)據(jù)類型時,為了提高系統(tǒng)效率,要注意4字節(jié)對齊原則……”。本文解釋x86上字節(jié)對齊的機制,其他架構(gòu)讀者可自行試驗。同時,本文對C/C++的函數(shù)調(diào)用方式進行了討論。
BTW想了幾天要在休息時間寫個總結(jié)但是直到今天18日才動手。寫出來應(yīng)該對自己還是有幫助的。也許還有一點點參考價值吧。由于本人水平所限,有不正確之處,歡迎大家提出。
感謝幾位同事。以及carrot。呵呵……
下面言歸正傳。
1.
先看下面的例子:
struct A{
??char c1;
??int i;
??short s;
??int j;
}a;
struct B{
??int i;
??int j;??
??short s;
??char c1;
}b;
結(jié)構(gòu)A沒有遵守字節(jié)對齊原則(為了區(qū)分,我將它叫做對齊聲明原則),結(jié)構(gòu)B遵守了。我們來看看在x86上會出現(xiàn)什么結(jié)果。先打印出a和b的各個成員的地址。會看到a中,各個成員間的間距是4個字節(jié)。b中,i和j,j和s都間距4個字節(jié),但是s和c1間距2個字節(jié)。所以:
sizeof(a) = 16
sizeof(b) = 12
為什么會有這樣的結(jié)果呢?這就是x86上字節(jié)對齊的作用。為了加快程序執(zhí)行的速度,一些體系結(jié)構(gòu)以對齊的方式設(shè)計,通常以字長作為對齊邊界。對于一些結(jié)構(gòu)體變量,整個結(jié)構(gòu)要對齊在內(nèi)部成員變量最大的對齊邊界,如B,整個結(jié)構(gòu)以4為對齊邊界,所以sizeof(b)為12,而不是11。
對于A來講,雖然聲明的時候沒有對齊,但是根據(jù)打印出的地址來看,編譯器已經(jīng)自動為其對齊了,所以每個成員的間距是4。在x86下,聲明A與B唯一的差別,僅在于A多浪費了4個字節(jié)內(nèi)存。(是不是某些特定情況下,B比A執(zhí)行更快,這個還需要討論。比如緊挨的兩條分別取s和c1的指令)
如果體系結(jié)構(gòu)是不對齊的,A中的成員將會一個挨一個存儲,從而sizeof(a)為11。顯然對齊更浪費了空間。那么為什么要使用對齊呢?
體系結(jié)構(gòu)的對齊和不對齊,是在時間和空間上的一個權(quán)衡。對齊節(jié)省了時間。假設(shè)一個體系結(jié)構(gòu)的字長為w,那么它同時就假設(shè)了在這種體系結(jié)構(gòu)上對寬度為w的數(shù)據(jù)的處理最頻繁也是最重要的。它的設(shè)計也是從優(yōu)先提高對w位數(shù)據(jù)操作的效率來考慮的。比如說讀寫時,大多數(shù)情況下需要讀寫w位數(shù)據(jù),那么數(shù)據(jù)通道就會是w位。如果所有的數(shù)據(jù)訪問都以w位對齊,那么訪問還可以進一步加快,因為需要傳輸?shù)牡刂肺粶p少,尋址可以加快。大多數(shù)體系結(jié)構(gòu)都是按照字長來對齊訪問數(shù)據(jù)的。不對齊的時候,有的會出錯,比如MIPS上會產(chǎn)生bus error,而x86則會進行多次訪問來拼接得到的結(jié)果,從而降低執(zhí)行效率。
有些體系結(jié)構(gòu)是必須要求對齊的,如sparc,MIPS。它們在硬件的設(shè)計上就強制性的要求對齊。不是因為它們作不到對齊的訪問,而是它們認為這樣沒有意義。它們追求的是速度。
上面講了體系結(jié)構(gòu)的對齊。在IA-32上面,sizeof(a)為16,就是對齊的結(jié)果。下面我們來看,為什么變量聲明的時候也要盡量對齊。
我們看到,結(jié)構(gòu)A的聲明并不對齊,但是它的成員地址仍是以4為邊界對齊的(成員間距為4)。這是編譯器的功勞。因為我所用的編譯器gcc,默認是對齊的。而x86可以處理不對齊的數(shù)據(jù)訪問,所以這樣聲明程序并不會出錯。但是對于其他結(jié)構(gòu),只能訪問對齊的數(shù)據(jù),而編譯器又不小心設(shè)置了不對齊的選項,則代碼就不能執(zhí)行了。如果按照B的方式聲明,則不管編譯器是否設(shè)置了對齊選項,都能夠正確的訪問數(shù)據(jù)。
目前的開發(fā)普遍比較重視性能,所以對齊的問題,有三種不同的處理方法:
1)????采用B的方式聲明
2)????對于邏輯上相關(guān)的成員變量希望放在靠近的位置,就寫成A的方式。有一種做法是顯式的插入reserved成員:
???????? struct A{
?????????? char c1;
?????????? char reserved1[3];
?????????? int i;
?????????? short s;
?????????? char reserved2[2];
?????????? int j;
}a;
3)????隨便怎么寫,一切交給編譯器自動對齊。
代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強制類型轉(zhuǎn)換的時候。下面舉個例子:
unsigned int ui_1=0x12345678;
unsigned char *p=NULL;
unsigned short *us_1=NULL;
p=&ui_1;
*p=0x00;
us_1=(unsigned short *)(p+1);
*us_1=0x0000;
最后兩句代碼,從奇數(shù)邊界去訪問unsigned short型變量,顯然不符合對齊的規(guī)定。在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個bus error(我沒有試)。
有些人喜歡通過移動指針來操作結(jié)構(gòu)中的成員(比如在linux操作struct sk_buff的成員),但是我們看到,A中(&c1+1) 決不等于&i。不過B中(&s+2)就是 &c1了。所以,我們清楚了結(jié)構(gòu)中成員的存放位置,才能編寫無錯的代碼。同時切記,不管對于結(jié)構(gòu),數(shù)組,或者普通的變量,在作強制類型轉(zhuǎn)換時一定要多看看:)不過為了不那么累,還是遵守聲明對齊原則吧!(這個原則是說變量盡量聲明在它的對齊邊界上,而且在節(jié)省空間的基礎(chǔ)上)
2.C/C++函數(shù)調(diào)用方式
我們當然早就知道,C/C++中的函數(shù)調(diào)用,都是以值傳遞的方式,而不是參數(shù)傳遞。那么,值傳遞是如何實現(xiàn)的呢?
函數(shù)調(diào)用前的典型匯編碼如下:
push?? %eax
call?? 0x401394 <test__Fc>
add????$0x10,%esp
首先,入棧的是實參的地址。由于被調(diào)函數(shù)都是對地址進行操作,所以就能夠理解值傳遞的原理和參數(shù)是引用時的情況了。
Call ***, 是要調(diào)用函數(shù)了,后面的地址,就是函數(shù)的入口地址。Call指令等價于:
?? PUSH IP
?? JMP ***
首先把當前的執(zhí)行地址IP壓棧,然后跳轉(zhuǎn)到函數(shù)執(zhí)行。
執(zhí)行完后,被調(diào)函數(shù)要返回,就要執(zhí)行RET指令。RET等價于POP IP,恢復(fù)CALL之前的執(zhí)行地址。所以一旦使用CALL指令,堆棧指針SP就會自動減2,因為IP的值進棧了。
函數(shù)的參數(shù)進棧的順序是從右到左,這是C與其它語言如pascal的不同之處。函數(shù)調(diào)用都以以下語句開始:
push?? %ebp
mov????%esp,%ebp
首先保存BP的值,然后將當前的堆棧指針傳遞給BP。那么現(xiàn)在BP+2就是IP的值(16位register的情況),BP+4放第一個參數(shù)的值,BP+6放第二個參數(shù)……。函數(shù)在結(jié)束前,要執(zhí)行POP BP。
????
C/C++語言默認的函數(shù)調(diào)用方式,都是由主調(diào)用函數(shù)進行參數(shù)壓棧并且恢復(fù)堆棧,實參的壓棧順序是從右到左,最后由主調(diào)函數(shù)進行堆棧恢復(fù)。由于主調(diào)用函數(shù)管理堆棧,所以可以實現(xiàn)變參函數(shù)。
對于WINAPI和CALLBACK函數(shù),在主調(diào)用函數(shù)中負責壓棧,在被調(diào)用函數(shù)中負責彈出堆棧中的參數(shù),并且負責恢復(fù)堆棧。因此不能實現(xiàn)變參函數(shù)。
(哪位對編譯原理和編譯器比較了解的,可以將這個部分寫完善,謝謝。可以加入編譯時的處理。不然只有等偶繼續(xù)學(xué)習(xí)了)
?
?
http://blog.programfan.com/article.asp?id=12008
結(jié)構(gòu)體的大小是一個令人迷惑不解的問題,不信,我出一題讓你算算看: enum DataType{IntData,CharData,VcharData}; struct Item??? { ???? char ItemNAme[30]; ?????? DataType ItemType; ?????? char ItemDecr[50]; ?????? int?ItemLength; }; 在你使用sizeof之前你能知道它的大小嗎?我想即使你用sizeof得出結(jié)果后,結(jié)果還是會讓你大吃一驚的:怎么是這個? ? 一.為什么要對齊? 《Windows核心編程》里這樣說:當CPU訪問正確對齊的數(shù)據(jù)時,它的運行效率最高,當數(shù)據(jù)大小的數(shù)據(jù)模數(shù)的內(nèi)存地址是0時,數(shù)據(jù)是對齊的。例如:WORD值應(yīng)該是總是從被2除盡的地址開始,而DWORD值應(yīng)該總是從被4除盡的地址開始,數(shù)據(jù)對齊不是內(nèi)存結(jié)構(gòu)的一部分,而是CPU結(jié)構(gòu)的一部分。當CPU試圖讀取的數(shù)值沒有正確的對齊時,CPU可以執(zhí)行兩種操作之一:產(chǎn)生一個異常條件;執(zhí)行多次對齊的內(nèi)存訪問,以便讀取完整的未對齊數(shù)據(jù),若多次執(zhí)行內(nèi)存訪問,應(yīng)用程序的運行速度就會慢。在最好的情況下,是兩倍的時間,有時更長。 ? 二.成員變量對齊的原理 我花了一個上午,看了一些資料,總算把這個問題搞明白了。下面我以一些例子說明結(jié)構(gòu)體成員變量的對齊問題。 對于 struct s1 { char a; long int d; double c; }; 這個結(jié)構(gòu)體的大小是16。編譯器默認的一般是8字節(jié)對齊。a的大小是1,它就按1字節(jié)對齊(因為比指定的8小),存諸在0偏移的地方;b大小為4,它就按4字節(jié)對齊(因為比指定的8小),存在偏移4——7的位置,c大小為8,存在8——15的位置。這樣3個成員共占用了16個字節(jié)。由于該結(jié)構(gòu)最大成員c大小為8,所以結(jié)構(gòu)按8字節(jié)對齊,16按8園整還是16,因此sizeof s1 = 16. 而對于 struct s2 { char a; long int d; ? double c; char e; }; 這個結(jié)構(gòu)體的大小是24。前3個成員和上面一樣存諸,d在4——7位置,c在8——15位置,但e按1字節(jié)對齊,存在偏移位置16,這樣4個成員占用了17個字節(jié)。由于該結(jié)構(gòu)的最大的數(shù)據(jù)成員c的大小是8個字節(jié),所以17對8園整得24。 ? 當然你可以使用#pragma指令指定編譯器按4字節(jié)對齊。即 #pragma pack(4)????? // 這里也可以是#pragma pack(push,4) ? struct s1 { char a; long int d; double c; }; ? struct s2 { char a; long int d; double c; char e; }; ? 這時s1的大小還是16,而s2的大小變?yōu)?0。我們來分析一下,對s1來說,按4字節(jié)對齊和按8字節(jié)對齊個數(shù)據(jù)成員的存諸位置是一樣的,只不過是最后一部園整時,16對4園整還是16。對s2就不一樣了,a的大小為1(比指定的4字節(jié)對齊要小),按1字節(jié)對齊,存諸在0位置,d的大小是4(大于等于指定的4字節(jié)),按4字節(jié)對齊,存諸在4——7位置,c的大小是8(大于指定的4字節(jié)),按4字節(jié)對齊,這樣存諸在8——15,e的大小是1,存儲在位置16,這樣整個結(jié)構(gòu)體的長度是17,17對4園整,得20。你也看到并不是指定編譯器按4字節(jié)對齊就按4字節(jié)對齊的。比如下面的結(jié)構(gòu)體: #pragma pack(4) struct TestStruct2 { ?? char m1[11]; ?? short m2; }; ?你知道它的大小嗎?是14。因為m1按1字節(jié)對齊,存諸在0——11位置,m2按2字節(jié)對齊,存諸在12——13位置。結(jié)構(gòu)體占用13個字節(jié),因為結(jié)構(gòu)體內(nèi)最大的成員的數(shù)據(jù)類型是short,大小為2,比指定的對齊字節(jié)4小,所以13對2園整,得14。綜的說來就是結(jié)構(gòu)體成員的對齊是用成員本身的大小和#pragma pack(push,n)中的n中較小的數(shù)對齊,例如如果成員大小為2,而你指定的對齊方式是4,則該成員按2對齊;結(jié)構(gòu)本身的對其是用結(jié)構(gòu)中最大成員的大小和#pragma pack(push,n)中的n較小的數(shù)對齊,即最后的園整,例如如果結(jié)構(gòu)中最大成員大小8,而你指定對齊是16,則結(jié)構(gòu)本身按8對齊。 ? 開頭題目的大小是92。你算到了嗎?下面這個結(jié)構(gòu)體的大小又是多少呢? enum DataType{IntData,CharData,VcharData}; #pragma pack(2) struct Item??? { ?????? char ItemNAme[30]; ?? DataType ItemType; ?? char ItemDecr[50]; ?? int?ItemLength; }; 內(nèi)存對齊與 struct型數(shù)據(jù)的內(nèi)存布局已提供該文章的PDF版,請在貼子末尾處下載。
當在C中定義了一個結(jié)構(gòu)類型時,它的大小是否等于各字段(field)大小之和?編譯器將如何在內(nèi)存中放置這些字段?ANSI C對結(jié)構(gòu)體的內(nèi)存布局有什么要求?而我們的程序又能否依賴這種布局?這些問題或許對不少朋友來說還有點模糊,那么本文就試著探究它們背后的秘密。 首先,至少有一點可以肯定,那就是ANSI C保證結(jié)構(gòu)體中各字段在內(nèi)存中出現(xiàn)的位置是隨它們的聲明順序依次遞增的,并且第一個字段的首地址等于整個結(jié)構(gòu)體實例的首地址。比如有這樣一個結(jié)構(gòu)體:
struct vector{int x,y,z;} s;
int *p,*q,*r;
struct vector *ps;
p = &s.x;
q = &s.y;
r = &s.z;
ps = &s;
assert(p < q);
assert(p < r);
assert(q < r);
assert((int*)ps == p);
// 上述斷言一定不會失敗
這時,有朋友可能會問:"標準是否規(guī)定相鄰字段在內(nèi)存中也相鄰?"。 唔,對不起,ANSI C沒有做出保證,你的程序在任何時候都不應(yīng)該依賴這個假設(shè)。那這是否意味著我們永遠無法勾勒出一幅更清晰更精確的結(jié)構(gòu)體內(nèi)存布局圖?哦,當然不是。不過先讓我們從這個問題中暫時抽身,關(guān)注一下另一個重要問題————內(nèi)存對齊。
許多實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制,它們會要求這些數(shù)據(jù)的首地址的值是某個數(shù)k(通常它為4或8)的倍數(shù),這就是所謂的內(nèi)存對齊,而這個k則被稱為該數(shù)據(jù)類型的對齊模數(shù)(alignment modulus)。當一種類型S的對齊模數(shù)與另一種類型T的對齊模數(shù)的比值是大于1的整數(shù),我們就稱類型S的對齊要求比T強(嚴格),而稱T比S弱(寬松)。這種強制的要求一來簡化了處理器與內(nèi)存之間傳輸系統(tǒng)的設(shè)計,二來可以提升讀取數(shù)據(jù)的速度。比如這么一種處理器,它每次讀寫內(nèi)存的時候都從某個8倍數(shù)的地址開始,一次讀出或?qū)懭?個字節(jié)的數(shù)據(jù),假如軟件能保證double類型的數(shù)據(jù)都從8倍數(shù)地址開始,那么讀或?qū)懸粋€double類型數(shù)據(jù)就只需要一次內(nèi)存操作。否則,我們就可能需要兩次內(nèi)存操作才能完成這個動作,因為數(shù)據(jù)或許恰好橫跨在兩個符合對齊要求的8字節(jié)內(nèi)存塊上。某些處理器在數(shù)據(jù)不滿足對齊要求的情況下可能會出錯,但是Intel的IA32架構(gòu)的處理器則不管數(shù)據(jù)是否對齊都能正確工作。不過Intel奉勸大家,如果想提升性能,那么所有的程序數(shù)據(jù)都應(yīng)該盡可能地對齊。Win32平臺下的微軟C編譯器(cl.exe for 80x86)在默認情況下采用如下的對齊規(guī)則: 任何基本數(shù)據(jù)類型T的對齊模數(shù)就是T的大小,即sizeof(T)。比如對于double類型(8字節(jié)),就要求該類型數(shù)據(jù)的地址總是8的倍數(shù),而char類型數(shù)據(jù)(1字節(jié))則可以從任何一個地址開始。Linux下的GCC奉行的是另外一套規(guī)則(在資料中查得,并未驗證,如錯誤請指正):任何2字節(jié)大小(包括單字節(jié)嗎?)的數(shù)據(jù)類型(比如short)的對齊模數(shù)是2,而其它所有超過2字節(jié)的數(shù)據(jù)類型(比如long,double)都以4為對齊模數(shù)。
現(xiàn)在回到我們關(guān)心的struct上來。ANSI C規(guī)定一種結(jié)構(gòu)類型的大小是它所有字段的大小以及字段之間或字段尾部的填充區(qū)大小之和。嗯?填充區(qū)?對,這就是為了使結(jié)構(gòu)體字段滿足內(nèi)存對齊要求而額外分配給結(jié)構(gòu)體的空間。那么結(jié)構(gòu)體本身有什么對齊要求嗎?有的,ANSI C標準規(guī)定結(jié)構(gòu)體類型的對齊要求不能比它所有字段中要求最嚴格的那個寬松,可以更嚴格(但此非強制要求,VC7.1就僅僅是讓它們一樣嚴格)。我們來看一個例子(以下所有試驗的環(huán)境是Intel Celeron 2.4G + WIN2000 PRO + vc7.1,內(nèi)存對齊編譯選項是"默認",即不指定/Zp與/pack選項):
typedef struct ms1
{
char a;
int b;
} MS1;
假設(shè)MS1按如下方式內(nèi)存布局(本文所有示意圖中的內(nèi)存地址從左至右遞增):
+---------------------------+
| | |
| a | b |
| | |
+---------------------------+
1 Byte 4 byte
因為MS1中有最強對齊要求的是b字段(int),所以根據(jù)編譯器的對齊規(guī)則以及ANSI C標準,MS1對象的首地址一定是4(int類型的對齊模數(shù))的倍數(shù)。那么上述內(nèi)存布局中的b字段能滿足int類型的對齊要求嗎?嗯,當然不能。如果你是編譯器,你會如何巧妙安排來滿足CPU的癖好呢?呵呵,經(jīng)過1毫秒的艱苦思考,你一定得出了如下的方案:
_______________________________________
| |///| |
| a |//padding//| b |
| |///| |
+-------------------------------------+
Bytes: 1 3 4
這個方案在a與b之間多分配了3個填充(padding)字節(jié),這樣當整個struct對象首地址滿足4字節(jié)的對齊要求時,b字段也一定能滿足int型的4字節(jié)對齊規(guī)定。那么sizeof(MS1)顯然就應(yīng)該是8,而b字段相對于結(jié)構(gòu)體首地址的偏移就是4。非常好理解,對嗎?現(xiàn)在我們把MS1中的字段交換一下順序:
typedef struct ms2
{
int a;
char b;
} MS2;
或許你認為MS2比MS1的情況要簡單,它的布局應(yīng)該就是
_______________________
| | |
| a | b |
| | |
+---------------------+
Bytes: 4 1
因為MS2對象同樣要滿足4字節(jié)對齊規(guī)定,而此時a的地址與結(jié)構(gòu)體的首地址相等,所以它一定也是4字節(jié)對齊。嗯,分析得有道理,可是卻不全面。讓我們來考慮一下定義一個MS2類型的數(shù)組會出現(xiàn)什么問題。C標準保證,任何類型(包括自定義結(jié)構(gòu)類型)的數(shù)組所占空間的大小一定等于一個單獨的該類型數(shù)據(jù)的大小乘以數(shù)組元素的個數(shù)。換句話說,數(shù)組各元素之間不會有空隙。按照上面的方案,一個MS2數(shù)組array的布局就是:
|<- array[1] ->|<- array[2] ->|<- array[3] .....
__________________________________________________________
| | | | |
| a | b | a | b |.............
| | | | |
+----------------------------------------------------------
Bytes: 4 1 4 1
當數(shù)組首地址是4字節(jié)對齊時,array[1].a也是4字節(jié)對齊,可是array[2].a呢?array[3].a ....呢?可見這種方案在定義結(jié)構(gòu)體數(shù)組時無法讓數(shù)組中所有元素的字段都滿足對齊規(guī)定,必須修改成如下形式:
___________________________________
| | |///|
| a | b |//padding//|
| | |///|
+---------------------------------+
Bytes: 4 1 3
現(xiàn)在無論是定義一個單獨的MS2變量還是MS2數(shù)組,均能保證所有元素的所有字段都滿足對齊規(guī)定。那么sizeof(MS2)仍然是8,而a的偏移為0,b的偏移是4。
好的,現(xiàn)在你已經(jīng)掌握了結(jié)構(gòu)體內(nèi)存布局的基本準則,嘗試分析一個稍微復(fù)雜點的類型吧。
typedef struct ms3
{
char a;
short b;
double c;
} MS3;
我想你一定能得出如下正確的布局圖:
padding
|
_____v_________________________________
| |/| |/| |
| a |/| b |/padding/| c |
| |/| |/| |
+-------------------------------------+
Bytes: 1 1 2 4 8
sizeof(short)等于2,b字段應(yīng)從偶數(shù)地址開始,所以a的后面填充一個字節(jié),而sizeof(double)等于8,c字段要從8倍數(shù)地址開始,前面的a、b字段加上填充字節(jié)已經(jīng)有4 bytes,所以b后面再填充4個字節(jié)就可以保證c字段的對齊要求了。sizeof(MS3)等于16,b的偏移是2,c的偏移是8。接著看看結(jié)構(gòu)體中字段還是結(jié)構(gòu)類型的情況:
typedef struct ms4
{
char a;
MS3 b;
} MS4;
MS3中內(nèi)存要求最嚴格的字段是c,那么MS3類型數(shù)據(jù)的對齊模數(shù)就與double的一致(為8),a字段后面應(yīng)填充7個字節(jié),因此MS4的布局應(yīng)該是:
_______________________________________
| |///| |
| a |//padding//| b |
| |///| |
+-------------------------------------+
Bytes: 1 7 16
顯然,sizeof(MS4)等于24,b的偏移等于8。
在實際開發(fā)中,我們可以通過指定/Zp編譯選項來更改編譯器的對齊規(guī)則。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告訴編譯器最大對齊模數(shù)是n。在這種情況下,所有小于等于n字節(jié)的基本數(shù)據(jù)類型的對齊規(guī)則與默認的一樣,但是大于n個字節(jié)的數(shù)據(jù)類型的對齊模數(shù)被限制為n。事實上,VC7.1的默認對齊選項就相當于/Zp8。仔細看看MSDN對這個選項的描述,會發(fā)現(xiàn)它鄭重告誡了程序員不要在MIPS和Alpha平臺上用/Zp1和/Zp2選項,也不要在16位平臺上指定/Zp4和/Zp8(想想為什么?)。改變編譯器的對齊選項,對照程序運行結(jié)果重新分析上面4種結(jié)構(gòu)體的內(nèi)存布局將是一個很好的復(fù)習(xí)。
到了這里,我們可以回答本文提出的最后一個問題了。結(jié)構(gòu)體的內(nèi)存布局依賴于CPU、操作系統(tǒng)、編譯器及編譯時的對齊選項,而你的程序可能需要運行在多種平臺上,你的源代碼可能要被不同的人用不同的編譯器編譯(試想你為別人提供一個開放源碼的庫),那么除非絕對必需,否則你的程序永遠也不要依賴這些詭異的內(nèi)存布局。順便說一下,如果一個程序中的兩個模塊是用不同的對齊選項分別編譯的,那么它很可能會產(chǎn)生一些非常微妙的錯誤。如果你的程序確實有很難理解的行為,不防仔細檢查一下各個模塊的編譯選項。
思考題:請分析下面幾種結(jié)構(gòu)體在你的平臺上的內(nèi)存布局,并試著尋找一種合理安排字段聲明順序的方法以盡量節(jié)省內(nèi)存空間。
A. struct P1 { int a; char b; int c; char d; };
B. struct P2 { int a; char b; char c; int d; };
C. struct P3 { short a[3]; char b[3]; };
D. struct P4 { short a[3]; char *b[3]; };
E. struct P5 { struct P2 *a; char b; struct P1 a[2]; };
參考資料:
【1】《深入理解計算機系統(tǒng)(修訂版)》,
(著)Randal E.Bryant; David O'Hallaron,
(譯)龔奕利 雷迎春,
中國電力出版社,2004
【2】《C: A Reference Manual》(影印版),
(著)Samuel P.Harbison; Guy L.Steele,
人民郵電出版社,2003
http://codesky.com/showthread.php?p=1
總結(jié)
以上是生活随笔為你收集整理的字节对齐和C/C++函数调用方式学习总结(多篇节选)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WebSphere通过corba调Tux
- 下一篇: 购房记---看房8