通過#pragma pack(n)改變C編譯器的字節對齊方式>>上的一個問題:
對於下面的結構體
struct test
{
char x1;
short x2;
float x3;
char x4;
};
結構各成員空間分配情況是怎樣的?
文章中解釋:
結構的第一個成員x1,其偏移地址為0,占據了第1個字節。第二個成員x2為short類型,其起始地址必須2字節對界,因此,編譯器在x2和x1之間填充了一個空字節。結構的第三個成員x3和第四個成員x4恰好落在其自然對界地址上,在它們前面不需要額外的填充字節。在 test結構中,成員x3要求4字節對界,是該結構所有成員中要求的最大對界單元,因而test結構的自然對界條件為4字節,編譯器在成員x4後面填充了 3個空字節。整個結構所占據空間為12字節。
同時,說明了更改C編譯器的缺省字節對齊方式: 在缺省情況下,C編譯器為每一個變量或是數據單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變缺省的對界條件:
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節對齊。
· 使用偽指令#pragma pack (),取消自定義字節對齊方式。
另外,還有如下的一種方式:
· __attribute((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。
· __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際占用字節數進行對齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。
下面是CSDN上提出的問題(原文<<intel和微軟和本公司同時出現的面試題>>:http://community.csdn.net/Expert/TopicView3.asp?id=3804035)
---------------------------------------
#pragma pack(8)
struct s1{
short a;
long b;
};
struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()
問
1.sizeof(s2) = ?
2.s2的c後面空了幾個字節接著是d?
---------------------------------------
下面是redleaves(ID最吊的網友)給出的解釋:
很詳盡,很透徹,從論壇原文後頭的對話可以看出redleaves對C/C++的標准理解很深刻,值得偶學習:)
#pragma pack(8)
struct S1{
char a;
long b;
};
struct S2 {
char c;
struct S1 d;
long long e;
};
#pragma pack()
sizeof(S2)結果為24.
成員對齊有一個重要的條件,即每個成員分別對齊.即每個成員按自己的方式對齊.
也就是說上面雖然指定了按8字節對齊,但並不是所有的成員都是以8字節對齊.其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這裡是8字節)中較小的一個對齊.並且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節.
S1中,成員a是1字節默認按1字節對齊,指定對齊參數為8,這兩個值中取1,a按1字節對齊;成員b是4個字節,默認是按4字節對齊,這時就按4字節對齊,所以sizeof(S1)應該為8;
S2 中,c和S1中的a一樣,按1字節對齊,而d 是個結構,它是8個字節,它按什麼對齊呢?對於結構來說,它的默認對齊方式就是它的所有成員使用的對齊參數中最大的一個,S1的就是4.所以,成員d就是按4字節對齊.成員e是8個字節,它是默認按8字節對齊,和指定的一樣,所以它對到8字節的邊界上,這時,已經使用了12個字節了,所以又添加了4個字節的空,從第16個字節開始放置成員e.這時,長度為24,已經可以被8(成員e按8字節對齊)整除.這樣,一共使用了24個字節.
a b
S1的內存布局:11**,1111,
c S1.a S1.b d
S2的內存布局:1***,11**,1111,****11111111
這裡有三點很重要:
1.每個成員分別按自己的方式對齊,並能最小化長度
2.復雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是復雜類型時,可以最小化長度
3.對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊
對於數組,比如:
char a[3];這種,它的對齊方式和分別寫3個char是一樣的.也就是說它還是按1個字節對齊.
如果寫: typedef char Array3[3];
Array3這種類型的對齊方式還是按1個字節對齊,而不是按它的長度.
不論類型是什麼,對齊的邊界一定是1,2,4,8,16,32,64....中的一個.
下面是從另外一篇文章(http://community.csdn.net/Expert/TopicView3.asp?id=3564500)的一些對各個類型以及結構體對齊方式的補充:
為了能使CPU對變量進行高效快速的訪問,變量的起始地址應該具有某些特性,即所謂的“對齊”。例如對於4字節的int類型變量,其起始地址應位於4字節邊界上,即起始地址能夠被4整除。變量的對齊規則如下(32位系統):
Type
Alignment
char
在字節邊界上對齊
short (16-bit)
在雙字節邊界上對齊
int and long (32-bit)
在4字節邊界上對齊
float
在4字節邊界上對齊
double
在8字節邊界上對齊
structures
單獨考慮結構體的個成員,它們在不同的字節邊界上對齊。
其中最大的字節邊界數就是該結構的字節邊界數。
如果結構體中有結構體成員,那麼這是一個遞歸的過程。
設編譯器設定的最大對齊字節邊界數為n,對於結構體中的某一成員item,它相對於結構首地址的實際字節對齊數
目X應該滿足以下規則:
X = min(n, sizeof(item))
例如,對於結構體
struct {
char a;
long b;
} T;
當位於32位系統,n=8時:
a的偏移為0,
b的偏移為4,中間填充了3個字節, b的X為4;
當位於32位系統,n=2時:
a的偏移為0,
b的偏移為2,中間填充了1個字節,b的X為2;
結構體的sizeof:
設結構體的最後一個成員為LastItem,其相對於結構體首地址的偏移為offset(LastItem),其大小為sizeof(LastItem),結構體的字節對齊數為N,則:結構體的sizeof 為: 若offset(LastItem)+ sizeof(LastItem)能夠被N整除,那麼就是offset(LastItem)+ sizeof(LastItem),否則,在後面填充,直到能夠被N整除。
另外:
1) 對於空結構體,sizeof == 1;因為必須保證結構體的每一個實例在內存中都有獨一無二的地址.
2) 結構體的靜態成員不對結構體的大小產生影響,因為靜態變量的存儲位置與結構體的實例地址無關。例如:
struct {static int I;} T; struct {char a; static int I;} T1;
sizeof(T) == 1; sizeof(T1) == 1;
要想更進一步了解編譯器對結構體及各變量對齊方式的處理,尤其是變量及結構體大小的用法(sizeof的用法)
推薦閱讀下面一篇文章(可惜沒有下篇:():
<<sizeof,終極無惑(上)>>: