struct {}node;
32為的x86,window下VC下sizeof(node)的值為1,而linux的gcc下值為0;
一、WINDOWS下(VC--其實GCC和其原理基本一樣,象這種問題,一般要查具體的編譯器設置)字節(jié)對齊的規(guī)則:
1、一般設置的對齊方式為1,2,4字節(jié)對齊方式,VC一般默認為4字節(jié)(最大為8字節(jié))。結(jié)構(gòu)的首地址必須是結(jié)構(gòu)內(nèi)最寬類型的整數(shù)倍地址;另外,結(jié)構(gòu)體的每一個成員起始地址必須是自身類型大小的整數(shù)倍(需要特別注意的是windows下是這樣的,但在linux的gcc編譯器下最高為4字節(jié)對齊),否則在前一類型后補0;這里特別提到的是數(shù)組一定要注意,而且在一些編程的技巧中,我們可以使用數(shù)組強制字節(jié)達到對齊的目的。這在網(wǎng)絡編程中是很常見的。
舉例:比如CHAR型占用空間為1字節(jié),則其起始位置必須可被1整除。INT為4字節(jié),其起始位置必須被4帶隊,依次類推。(我們假定類或結(jié)構(gòu)體的起始位置為0位置,其實編譯器是在開辟空間時,會尋找起始位置可被結(jié)構(gòu)內(nèi)最寬類型整除的地址做為開始地址,因此我們可以假定其為0值,因為這0值可以被任意的類型整除。)
2、結(jié)構(gòu)體的整體大小必須可被對齊值整除,默認4(默認,且結(jié)構(gòu)中的類型大小都小于默認的4)。
3、結(jié)構(gòu)體的整體大小必須可被本結(jié)構(gòu)內(nèi)的最寬類型整除。(其實和上一條是一樣的,但這里獨立出來,起注意作用。比如結(jié)構(gòu)體里的有DOUBLE,那么結(jié)構(gòu)的大小最后必須可被8整除)
注意:GCC不是這樣,就是最高只能被4整除,它是個死的。
否則(2、3條),編譯器會在結(jié)構(gòu)的最后添充一定的特定字符來補齊。
struct T
{
char ch;
double d ;
};
在VC中是16個字節(jié),GCC中為12個字節(jié)。
4、對于結(jié)構(gòu)體內(nèi)嵌套結(jié)構(gòu)體的形勢,規(guī)定是必須按照基本數(shù)據(jù)類型來定義,而不能以嵌套結(jié)構(gòu)大小來做為上三種使用的基準。
二、舉例:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
struct C
{
double t;
char b;
int a;
short c;
};
struct D
{
char b;
double t;
int a;
short c;
};
在VC中,SIZEOF這四個結(jié)構(gòu)體,分別為:8、12、24、24;
我們先談第一個,(說明一下,在考慮結(jié)構(gòu)體大小時,我們基本可以忽略起始地址的問題,因為這個編譯器會自動為我們做好,見上面的說明),結(jié)構(gòu)體內(nèi)首先是一個INT的4字節(jié),起始地址假定為0,整除4,其小于等于默認的4字節(jié)對齊且0為4(INT的占用空間)的整數(shù)倍,所以,其占四個字節(jié);其后為起始地址為5,空間為1個字節(jié)的CHAR,小于4且5為1(CHAR占用空間)的整數(shù)倍,故占用1個字節(jié),然后是一個起始地址為5占2個字節(jié)的SHORT,其小于4,但5不為2位數(shù),故補齊一個字節(jié),從第6個字節(jié)開始,占2字節(jié)空間。所以共占用4+1+1(補)+2=8;8/4=2;整除,故占用8字節(jié)空間。
再談第2個,CHAR不用解釋,占有一個字節(jié)空間,且可以被0地址整除。而INT則占4字節(jié)空間,所以其必須在CHAR后補齊3字節(jié),到第四個字節(jié),才是INT的真正地址。SHORT也不用說,所以共占有:1+3(補)+4+2=10個字節(jié),但10不能整除4,所以要在結(jié)構(gòu)體最后補齊2字節(jié)。故實際占有10+2= 12個字節(jié)。
談第三個,C結(jié)構(gòu)體只是在B結(jié)構(gòu)體前加了一個DOUBLE,其它都一樣,按說應該是20個字節(jié)啊,但注意我們上面規(guī)則的第3條。必須是最寬類型的整數(shù)倍,一定要分清,所以得補齊到24,D結(jié)構(gòu)體類似,不再講。
三、結(jié)構(gòu)體的中含有位域
這個東西用得比較少,但還是總結(jié)一下:
如果結(jié)構(gòu)體中含有位域(bit-field),那么VC中準則又要有所更改:
1) 如果相鄰位域字段的類型相同,且其位寬之和小于類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大于類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數(shù)倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現(xiàn)有差異,VC6采取不壓縮方式(不同位域字段存放在不同的位域類型字節(jié)中),Dev-C++和GCC都采取壓縮方式;
備注:當兩字段類型不一樣的時候,對于不壓縮方式,例如:
struct N
{
char c:2;
int i:4;
};
依然要滿足不含位域結(jié)構(gòu)體內(nèi)存對齊準則第2條,i成員相對于結(jié)構(gòu)體首地址的偏移應該是4的整數(shù)倍,所以c成員后要填充3個字節(jié),然后再開辟4個字節(jié)的空間作為int型,其中4位用來存放i,所以上面結(jié)構(gòu)體在VC中所占空間為8個字節(jié);而對于采用壓縮方式的編譯器來說,遵循不含位域結(jié)構(gòu)體內(nèi)存對齊準則第2條,不同的是,如果填充的3個字節(jié)能容納后面成員的位,則壓縮到填充字節(jié)中,不能容納,則要單獨開辟空間,所以上面結(jié)構(gòu)體N在GCC或者Dev-C++中所占空間應該是4個字節(jié)。
4) 如果位域字段之間穿插著非位域字段,則不進行壓縮;
備注:
結(jié)構(gòu)體
typedef struct
{
char c:2;
double i;
int c2:4;
}N3;
在GCC下占據(jù)的空間為16字節(jié),在VC下占據(jù)的空間應該是24個字節(jié)。
四、字節(jié)對齊的控制方法
主要是使用:
#pragma pack (2) /*指定按2字節(jié)對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
大家如果有興趣,可以自己上機調(diào)一下各種對齊方式下的占用空間大小,這里就不再舉例。
#pragma pack(push) //保存對齊狀態(tài)
#pragma pack(4)//設定為4字節(jié)對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢復對齊狀態(tài)
這里需要注意的是,如果對齊的字節(jié)非為1、2、4、8等可整除位數(shù),則自動默認回默認的對齊字節(jié)數(shù),這個我沒有測試,大家可以試一下,應該沒什么問題。
五、多編譯器的使用:(其下為轉(zhuǎn)載)
為了防止不同編譯器對齊不一樣,建議在代碼里面指定對齊參數(shù)
可能重要的一點是關于緊縮結(jié)構(gòu)的。緊縮結(jié)構(gòu)的用途 其實最常用的結(jié)構(gòu)對齊選項就是:默認對齊和緊縮。在兩個程序,或者兩個平臺之間傳遞數(shù)據(jù)時,我們通常會將數(shù)據(jù)結(jié)構(gòu)設置為緊縮的。這樣不僅可以減小通信量,還可以避免對齊帶來的麻煩。假設甲乙雙方進行跨平臺通信,甲方使用了“/Zp2”這么奇怪的對齊選項,而乙方的編譯器不支持這種對齊方式,那么乙方就可以理解什么叫欲哭無淚了。 當我們需要一個字節(jié)一個字節(jié)訪問結(jié)構(gòu)數(shù)據(jù)時,我們通常都會希望結(jié)構(gòu)是緊縮的,這樣就不必考慮哪個字節(jié)是填充字節(jié)了。我們把數(shù)據(jù)保存到非易失設備時,通常也會采用緊縮結(jié)構(gòu),既減小存儲量,也方便其它程序讀出。各編譯器都支持結(jié)構(gòu)的緊縮,即連續(xù)排列結(jié)構(gòu)的各成員變量,各成員變量之間沒有任何填充字節(jié)。這時,結(jié)構(gòu)的大小等于各成員變量大小的和。緊縮結(jié)構(gòu)的變量可以放在1n邊界,即任意地址邊界。在GNU gcc:
typedef struct St2Tag
{
St1 st1;
char ch2;
}
__attribute__ ((packed)) St2;
在ARMCC:
typedef __packed struct St2Tag
{
St1 st1;
char ch2;
} St2;
在VC:
#pragma pack(1)
typedef struct St2Tag
{
St1 st1;
char ch2;
} St2;
#pragma pack()
針對不同的編譯器:
#ifdef __GNUC__
#define GNUC_PACKED __attribute__ ((packed))
#else
#define GNUC_PACKED
#endif
#ifdef __arm
#define ARM_PACKED __packed
#else
#define ARM_PACKED
#endif
#ifdef WIN32
#pragma pack(1)
#endif
typedef ARM_PACKED struct St2Tag
{
St1 st1;
char ch2;
}
GNUC_PACKED St2;
#ifdef WIN32
#pragma pack()
#endif
最后記錄一個小細節(jié)。gcc編譯器和VC編譯器都支持在緊縮結(jié)構(gòu)中包含非緊縮結(jié)構(gòu),例如前面例子中的St2可以包含非緊縮的St1。但對于ARM編譯器而言,緊縮結(jié)構(gòu)包含的其它結(jié)構(gòu)必須是緊縮的。如果緊縮的St2包含了非緊縮的St1,編譯時就會報錯:
C語言的字節(jié)對齊及#pragma pack的使用
2010-04-16 09:44:33| 分類: vc/c/c++ | 標簽: |字號大中小 訂閱
C編譯器的缺省字節(jié)對齊方式(自然對界)
在缺省情況下,C編譯器為每一個變量或是數(shù)據(jù)單元按其自然對界條件分配空間。
在結(jié)構(gòu)中,編譯器為結(jié)構(gòu)的每個成員按其自然對界(alignment)條件分配空間。各個成員按照它們被聲明的順序在內(nèi)存中順序存儲(成員之間可能有插入的空字節(jié)),第一個成員的地址和整個結(jié)構(gòu)的地址相同。
C編譯器缺省的結(jié)構(gòu)成員自然對界條件為“N字節(jié)對齊”,N即該成員數(shù)據(jù)類型的長度。如int型成員的自然對界條件為4字節(jié)對齊,而double類型的結(jié)構(gòu)成員的自然對界條件為8字節(jié)對齊。若該成員的起始偏移不位于該成員的“默認自然對界條件”上,則在前一個節(jié)面后面添加適當個數(shù)的空字節(jié)。
C編譯器缺省的結(jié)構(gòu)整體的自然對界條件為:該結(jié)構(gòu)所有成員中要求的最大自然對界條件。若結(jié)構(gòu)體各成員長度之和不為“結(jié)構(gòu)整體自然對界條件的整數(shù)倍,則在最后一個成員后填充空字節(jié)。
例子1(分析結(jié)構(gòu)各成員的默認字節(jié)對界條界條件和結(jié)構(gòu)整體的默認字節(jié)對界條件):
struct Test
{
char x1; // 成員x1為char型(其起始地址必須1字節(jié)對界),其偏移地址為0
char x2; // 成員x2為char型(其起始地址必須1字節(jié)對界,其偏移地址為1
float x3; // 成員x3為float型(其起始地址必須4字節(jié)對界),編譯器在x2和x3之間填充了兩個空字節(jié),其偏移地址為4
char x4; // 成員x4為char型(其起始地址必須1字節(jié)對界),其偏移地址為8
};
因為Test結(jié)構(gòu)體中,最大的成員為flaot x3,因些此結(jié)構(gòu)體的自然對界條件為4字節(jié)對齊。則結(jié)構(gòu)體長度就為12字節(jié),內(nèi)存布局為1100 1111 1000。
例子2:
#include <stdio.h>
//#pragma pack(2)
typedef struct
{
int aa1; //4個字節(jié)對齊 1111
char bb1;//1個字節(jié)對齊 1
short cc1;//2個字節(jié)對齊 011
char dd1; //1個字節(jié)對齊 1
} testlength1;
int length1 = sizeof(testlength1); //4個字節(jié)對齊,占用字節(jié)1111 1011 1000,length = 12
typedef struct
{
char bb2;//1個字節(jié)對齊 1
int aa2; //4個字節(jié)對齊 01111
short cc2;//2個字節(jié)對齊 11
char dd2; //1個字節(jié)對齊 1
} testlength2;
int length2 = sizeof(testlength2); //4個字節(jié)對齊,占用字節(jié)1011 1111 1000,length = 12
typedef struct
{
char bb3; //1個字節(jié)對齊 1
char dd3; //1個字節(jié)對齊 1
int aa3; //4個字節(jié)對齊 001111
short cc23//2個字節(jié)對齊 11
} testlength3;
int length3 = sizeof(testlength3); //4個字節(jié)對齊,占用字節(jié)1100 1111 1100,length = 12
typedef struct
{
char bb4; //1個字節(jié)對齊 1
char dd4; //1個字節(jié)對齊 1
short cc4;//2個字節(jié)對齊 11
int aa4; //4個字節(jié)對齊 1111
} testlength4;
int length4 = sizeof(testlength4); //4個字節(jié)對齊,占用字節(jié)1111 1111,length = 8
int main(void)
{
printf("length1 = %d.\n",length1);
printf("length2 = %d.\n",length2);
printf("length3 = %d.\n",length3);
printf("length4 = %d.\n",length4);
return 0;
}
改變?nèi)笔〉膶鐥l件(指定對界)
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節(jié)對齊。
· 使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。
這時,對齊規(guī)則為:
1、數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的對齊按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進行。
2、結(jié)構(gòu)(或聯(lián)合)的整體對齊規(guī)則:在數(shù)據(jù)成員完成各自對齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進行對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中,比較小的那個進行。
結(jié)合1、2推斷:當#pragma pack的n值等于或超過所有數(shù)據(jù)成員長度的時候,這個n值的大小將不產(chǎn)生任何效果。
因此,當使用偽指令#pragma pack (2)時,Test結(jié)構(gòu)體的大小為8,內(nèi)存布局為11 11 11 10。
需要注意一點,當結(jié)構(gòu)體中包含一個子結(jié)構(gòu)體時,子結(jié)構(gòu)中的成員按照#pragma pack指定的數(shù)值和子結(jié)構(gòu)最大數(shù)據(jù)成員長度中,比較小的那個進行進行對齊。例子如下:
#pragma pack(8)
struct s1{
short a;
long b;
};
struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()
sizeof(s2)的結(jié)果為24。S1的內(nèi)存布局為1100 1111,S2的內(nèi)存布局為1000 1100 1111 0000 1111 1111。
例子:
#include <stdio.h>
#pragma pack(2)
typedef struct
{
int aa1; //2個字節(jié)對齊 1111
char bb1;//1個字節(jié)對齊 1
short cc1;//2個字節(jié)對齊 011
char dd1; //1個字節(jié)對齊 1
} testlength1;
int length1 = sizeof(testlength1); //2個字節(jié)對齊,占用字節(jié)11 11 10 11 10,length = 10
typedef struct
{
char bb2;//1個字節(jié)對齊 1
int aa2; //2個字節(jié)對齊 01111
short cc2;//2個字節(jié)對齊 11
char dd2; //1個字節(jié)對齊 1
} testlength2;
int length2 = sizeof(testlength2); //2個字節(jié)對齊,占用字節(jié)10 11 11 11 10,length = 10
typedef struct
{
char bb3; //1個字節(jié)對齊 1
char dd3; //1個字節(jié)對齊 1
int aa3; //2個字節(jié)對齊 11 11
short cc23//2個字節(jié)對齊 11
} testlength3;
int length3 = sizeof(testlength3); //2個字節(jié)對齊,占用字節(jié)11 11 11 11,length = 8
typedef struct
{
char bb4; //1個字節(jié)對齊 1
char dd4; //1個字節(jié)對齊 1
short cc4;//2個字節(jié)對齊 11
int aa4; //2個字節(jié)對齊 11 11
} testlength4;
int length4 = sizeof(testlength4); //2個字節(jié)對齊,占用字節(jié)11 11 11 11,length = 8
int main(void)
{
printf("length1 = %d.\n",length1);
printf("length2 = %d.\n",length2);
printf("length3 = %d.\n",length3);
printf("length4 = %d.\n",length4);
return 0;
}
另外,還有如下的一種方式:
· __attribute((aligned (n))),讓所作用的結(jié)構(gòu)成員對齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長度大于n,則按照最大成員的長度來對齊。
· __attribute__ ((packed)),取消結(jié)構(gòu)在編譯過程中的優(yōu)化對齊,按照實際占用字節(jié)數(shù)進行對齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。