arm 結構體對齊問(wèn)題
這段時(shí)間移植公司的linux i386程序到Arm linux平臺,本以為是件工作量很小的事情,以為只要改幾個(gè)驅動(dòng)程序就OK了,沒(méi)想到在應用程序這一塊卡了很長(cháng)時(shí)間。其中最煩的事情就莫過(guò)于結構體內存邊界對齊了。搞了這么久,終于終結了一些小經(jīng)驗。
默認情況下,在32位cpu里,gcc對于結構體的對齊方式是按照四個(gè)字節來(lái)對齊的??匆韵陆Y構體
typedef struct pack{
char a;
int b;
short c;
}pack;
對于Pack結構體,默認情況下在arm/386平臺下(別的平臺沒(méi)試過(guò))sizeof(pack)=12,求解過(guò)程如下:
sizeof(char)=1;
下一個(gè)int b,由于是四個(gè)字節,要求b的開(kāi)始地址從32的整數倍開(kāi)始,故需要在a后面填充3個(gè)沒(méi)用的字節,記為dump(3),sizeof(b)=4,此時(shí)相當于結構體擴充為
char a;
char dump(3);
int b;
看short c,現在c的前面有8個(gè)字節,c是兩個(gè)字節,c的開(kāi)始地址是從16的整數開(kāi)始,在b前面不需再加東西.此時(shí)對于結構體來(lái)說(shuō),sizeof(pack)=10,但是這不是最終結果,最后總的字節數也要能被4個(gè)字節整除,所以還需在short c后面再加
dump(2);
故總的字節數為12.
當然以上說(shuō)的只是簡(jiǎn)單的情況,下面談?wù)凙rm,x86在gcc里關(guān)于內存邊界字節對齊的區別.對于同樣的結構體,在386下
#prama pack(1)
后,sizeof(pack)=1 4 2=7
而在arm下同樣的操作sizeof(pack)=1 4 2 1=8,即雖然b根a之間不要填充但總的長(cháng)度必須要是4的整數倍.
在A(yíng)RM 下要使結構體按指定字節對齊,可行的方法
1.在makefile里加-fpack-struct 選項,這樣的話(huà)對所有的結構按一字節對齊.
不得不說(shuō),確實(shí)有那么些質(zhì)量較差的程序可能需要你部分自然對齊,部分一字 節對齊,此時(shí)
2. typedef struct pack{
}__attribute__((packed))
可利用__attribute__屬性
當然最后的方式,還是自己去看ARM體系結構與gcc編譯選項了。
------------------------------------------------------------------------------------------------------------
淺談結構體對齊問(wèn)題
#include
int main() {
struct ms {
double x;
char a;
int y;
};
// }__attribute__((packed));
printf("%d/n", sizeof(struct ms));
return 0;
}
linux上運行,結果為16;如果采用注釋的那一行,則結果為13
原文::http://dev.csdn.net/article/48/48195.shtm
什么是內存對齊
考慮下面的結構:
struct foo
{
char c1;
short s;
char c2;
int i;
};
假設這個(gè)結構的成員在內存中是緊湊排列的,假設c1的地址是0,那么s的地址就應該是1,c2的地址就是3,i的地址就是4。也就是
c1 00000000, s 00000001, c2 00000003, i 00000004。
可是,我們在Visual c/c++ 6中寫(xiě)一個(gè)簡(jiǎn)單的程序:
struct foo a;
printf("c1 %p, s %p, c2 %p, i %p/n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
運行,輸出:
c1 00000000, s 00000002, c2 00000004, i 00000008。
為什么會(huì )這樣?這就是內存對齊而導致的問(wèn)題。
為什么會(huì )有內存對齊
以下內容節選自《Intel Architecture 32 Manual》。
字,雙字,和四字在自然邊界上不需要在內存中對齊。(對字,雙字,和四字來(lái)說(shuō),自然邊界分別是偶數地址,可以被4整除的地址,和可以被8整除的地址。)
無(wú)論如何,為了提高程序的性能,數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪(fǎng)問(wèn)未對齊的內存,處理器需要作兩次內存訪(fǎng)問(wèn);然而,對齊的內存訪(fǎng)問(wèn)僅需要一次訪(fǎng)問(wèn)。
一個(gè)字或雙字操作數跨越了4字節邊界,或者一個(gè)四字操作數跨越了8字節邊界,被認為是未對齊的,從而需要兩次總線(xiàn)周期來(lái)訪(fǎng)問(wèn)內存。一個(gè)字起始地址是奇數但卻沒(méi)有跨越字邊界被認為是對齊的,能夠在一個(gè)總線(xiàn)周期中被訪(fǎng)問(wèn)。
某些操作雙四字的指令需要內存操作數在自然邊界上對齊。如果操作數沒(méi)有對齊,這些指令將會(huì )產(chǎn)生一個(gè)通用保護異常(#GP)。雙四字的自然邊界是能夠被16 整除的地址。其他的操作雙四字的指令允許未對齊的訪(fǎng)問(wèn)(不會(huì )產(chǎn)生通用保護異常),然而,需要額外的內存總線(xiàn)周期來(lái)訪(fǎng)問(wèn)內存中未對齊的數據。
編譯器對內存對齊的處理
缺省情況下,c/c++編譯器默認將結構、棧中的成員數據進(jìn)行內存對齊。因此,上面的程序輸出就變成了:
c1 00000000, s 00000002, c2 00000004, i 00000008。
編譯器將未對齊的成員向后移,將每一個(gè)都成員對齊到自然邊界上,從而也導致了整個(gè)結構的尺寸變大。盡管會(huì )犧牲一點(diǎn)空間(成員之間有空洞),但提高了性能。
也正是這個(gè)原因,我們不可以斷言sizeof(foo) == 8。在這個(gè)例子中,sizeof(foo) == 12。
如何避免內存對齊的影響
那么,能不能既達到提高性能的目的,又能節約一點(diǎn)空間呢?有一點(diǎn)小技巧可以使用。比如我們可以將上面的結構改成:
struct bar
{
char c1;
char c2;
short s;
int i;
};
這樣一來(lái),每個(gè)成員都對齊在其自然邊界上,從而避免了編譯器自動(dòng)對齊。在這個(gè)例子中,sizeof(bar) == 8。
這個(gè)技巧有一個(gè)重要的作用,尤其是這個(gè)結構作為API的一部分提供給第三方開(kāi)發(fā)使用的時(shí)候。第三方開(kāi)發(fā)者可能將編譯器的默認對齊選項改變,從而造成這個(gè)結構在你的發(fā)行的DLL中使用某種對齊方式,而在第三方開(kāi)發(fā)者哪里卻使用另外一種對齊方式。這將會(huì )導致重大問(wèn)題。
比如,foo結構,我們的DLL使用默認對齊選項,對齊為
c1 00000000, s 00000002, c2 00000004, i 00000008,同時(shí)sizeof(foo) == 12。
而第三方將對齊選項關(guān)閉,導致
c1 00000000, s 00000001, c2 00000003, i 00000004,同時(shí)sizeof(foo) == 8。
如何使用c/c++中的對齊選項
vc6中的編譯選項有 /Zp[1|2|4|8|16] ,/Zp1表示以1字節邊界對齊,相應的,/Zpn表示以n字節邊界對齊。n字節邊界對齊的意思是說(shuō),一個(gè)成員的地址必須安排在成員的尺寸的整數倍地址上或者是n的整數倍地址上,取它們中的最小值。也就是:
min ( sizeof ( member ), n)
實(shí)際上,1字節邊界對齊也就表示了結構成員之間沒(méi)有空洞。
/Zpn選項是應用于整個(gè)工程的,影響所有的參與編譯的結構。
要使用這個(gè)選項,可以在vc6中打開(kāi)工程屬性頁(yè),c/c++頁(yè),選擇Code Generation分類(lèi),在Struct member alignment可以選擇。
要專(zhuān)門(mén)針對某些結構定義使用對齊選項,可以使用#pragma pack編譯指令。指令語(yǔ)法如下:
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
意義和/Zpn選項相同。比如:
#pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
棧內存對齊
我們可以觀(guān)察到,在vc6中棧的對齊方式不受結構成員對齊選項的影響。(本來(lái)就是兩碼事)。它總是保持對齊,而且對齊在4字節邊界上。
驗證代碼
#include
struct foo
{
char c1;
short s;
char c2;
int i;
};
struct bar
{
char c1;
char c2;
short s;
int i;
};
#pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
int main(int argc, char* argv[])
{
char c1;
short s;
char c2;
int i;
struct foo a;
struct bar b;
struct foo_pack p;
printf("stack c1 %p, s %p, c2 %p, i %p/n",
(unsigned int)(void*)&c1 - (unsigned int)(void*)&i,
(unsigned int)(void*)&s - (unsigned int)(void*)&i,
(unsigned int)(void*)&c2 - (unsigned int)(void*)&i,
(unsigned int)(void*)&i - (unsigned int)(void*)&i);
printf("struct foo c1 %p, s %p, c2 %p, i %p/n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
printf("struct bar c1 %p, c2 %p, s %p, i %p/n",
(unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.s - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.i - (unsigned int)(void*)&b);
printf("struct foo_pack c1 %p, s %p, c2 %p, i %p/n",
(unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.s - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.i - (unsigned int)(void*)&p);
printf("sizeof foo is %d/n", sizeof(foo));
printf("sizeof bar is %d/n", sizeof(bar));
printf("sizeof foo_pack is %d/n", sizeof(foo_pack));
return 0;
}
-----------------------------------------------------------------------------------------------------------在結構中,編譯器為結構的每個(gè)成員按其自然對界條件分配空間;各個(gè)成員按照它們被聲明的順序在內存中順序存儲,第一個(gè)成員的地址和整個(gè)結構的地址相同。在缺省情況下,c編譯器為每一個(gè)變量或是數據單元按其自然對界條件分配空間
例如,下面的結構各成員空間分配情況
struct test {
char x1;
short x2;
float x3;
char x4;
};
結構的第一個(gè)成員x1,其偏移地址為0,占據了第1個(gè)字節。第二個(gè)成員x2為short類(lèi)型,其起始地址必須2字節對界,因此,編譯器在x2和x1之間填充了一個(gè)空字節。結構的第三個(gè)成員x3和第四個(gè)成員x4恰好落在其自然對界地址上,在它們前面不需要額外的填充字節。在test結構中,成員x3要求 4字節對界,是該結構所有成員中要求的最大對界單元,因而test結構的自然對界條件為4字節,編譯器在成員x4后面填充了3個(gè)空字節。整個(gè)結構所占據空間為12字節。
現在你知道怎么回事了吧?
更改c編譯器的缺省分配策略
一般地,可以通過(guò)下面的兩種方法改變缺省的對界條件:
· 使用偽指令#pragma pack ([n])
· 在編譯時(shí)使用命令行參數
#pragma pack ([n])偽指令允許你選擇編譯器為數據分配空間所采取的對界策略:
例如,在使用了#pragma pack (1)偽指令后,test結構各成員的空間分配情況就是按照一個(gè)字節對齊了
#pragma pack(push) //保存對齊狀態(tài)
#pragma pack(1)
#pragma pack(pop)
評論