字節對齊(強制對齊以及自然對齊)


struct {}node;

32為的x86,window下VC下sizeof(node)的值為1,而linux的gcc下值為0;


一、WINDOWS下(VC--其實GCC和其原理基本一樣,象這種問題,一般要查具體的編譯器設置)字節對齊的規則:

1、一般設置的對齊方式為1,2,4字節對齊方式,VC一般默認為4字節(最大為8字節)。結構的首地址必須是結構內最寬類型的整數倍地址;另外,結構體的每一個成員起始地址必須是自身類型大小的整數倍(需要特別注意的是windows下是這樣的,但在linux的gcc編譯器下最高為4字節對齊),否則在前一類型后補0;這里特別提到的是數組一定要注意,而且在一些編程的技巧中,我們可以使用數組強制字節達到對齊的目的。這在網絡編程中是很常見的。

舉例:比如CHAR型占用空間為1字節,則其起始位置必須可被1整除。INT為4字節,其起始位置必須被4帶隊,依次類推。(我們假定類或結構體的起始位置為0位置,其實編譯器是在開辟空間時,會尋找起始位置可被結構內最寬類型整除的地址做為開始地址,因此我們可以假定其為0值,因為這0值可以被任意的類型整除。)

2、結構體的整體大小必須可被對齊值整除,默認4(默認,且結構中的類型大小都小於默認的4)。

3、結構體的整體大小必須可被本結構內的最寬類型整除。(其實和上一條是一樣的,但這里獨立出來,起注意作用。比如結構體里的有DOUBLE,那么結構的大小最后必須可被8整除)

注意:GCC不是這樣,就是最高只能被4整除,它是個死的。

否則(2、3條),編譯器會在結構的最后添充一定的特定字符來補齊。

struct T
{
char ch;
double d ;
};

在VC中是16個字節,GCC中為12個字節。

4、對於結構體內嵌套結構體的形勢,規定是必須按照基本數據類型來定義,而不能以嵌套結構大小來做為上三種使用的基准。

二、舉例:

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這四個結構體,分別為:8、12、24、24;

我們先談第一個,(說明一下,在考慮結構體大小時,我們基本可以忽略起始地址的問題,因為這個編譯器會自動為我們做好,見上面的說明),結構體內首先是一個INT的4字節,起始地址假定為0,整除4,其小於等於默認的4字節對齊且0為4(INT的占用空間)的整數倍,所以,其占四個字節;其后為起始地址為5,空間為1個字節的CHAR,小於4且5為1(CHAR占用空間)的整數倍,故占用1個字節,然后是一個起始地址為5占2個字節的SHORT,其小於4,但5不為2位數,故補齊一個字節,從第6個字節開始,占2字節空間。所以共占用4+1+1(補)+2=8;8/4=2;整除,故占用8字節空間。

再談第2個,CHAR不用解釋,占有一個字節空間,且可以被0地址整除。而INT則占4字節空間,所以其必須在CHAR后補齊3字節,到第四個字節,才是INT的真正地址。SHORT也不用說,所以共占有:1+3(補)+4+2=10個字節,但10不能整除4,所以要在結構體最后補齊2字節。故實際占有10+2= 12個字節。

談第三個,C結構體只是在B結構體前加了一個DOUBLE,其它都一樣,按說應該是20個字節啊,但注意我們上面規則的第3條。必須是最寬類型的整數倍,一定要分清,所以得補齊到24,D結構體類似,不再講。

三、結構體的中含有位域

這個東西用得比較少,但還是總結一下:

如果結構體中含有位域(bit-field),那么VC中准則又要有所更改:
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6采取不壓縮方式(不同位域字段存放在不同的位域類型字節中),Dev-C++和GCC都采取壓縮方式;
備注:當兩字段類型不一樣的時候,對於不壓縮方式,例如:

struct N
{
char c:2;
int i:4;
};

依然要滿足不含位域結構體內存對齊准則第2條,i成員相對於結構體首地址的偏移應該是4的整數倍,所以c成員后要填充3個字節,然后再開辟4個字節的空間作為int型,其中4位用來存放i,所以上面結構體在VC中所占空間為8個字節;而對於采用壓縮方式的編譯器來說,遵循不含位域結構體內存對齊准則第2條,不同的是,如果填充的3個字節能容納后面成員的位,則壓縮到填充字節中,不能容納,則要單獨開辟空間,所以上面結構體N在GCC或者Dev-C++中所占空間應該是4個字節。

4) 如果位域字段之間穿插着非位域字段,則不進行壓縮;
備注:
結構

typedef struct
{
char c:2;
double i;
int c2:4;
}N3;

在GCC下占據的空間為16字節,在VC下占據的空間應該是24個字節。

四、字節對齊的控制方法

主要是使用:

#pragma pack (2) /*指定按2字節對齊*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/

大家如果有興趣,可以自己上機調一下各種對齊方式下的占用空間大小,這里就不再舉例。

#pragma pack(push) //保存對齊狀態
#pragma pack(4)//設定為4字節對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢復對齊狀態

這里需要注意的是,如果對齊的字節非為1、2、4、8等可整除位數,則自動默認回默認的對齊字節數,這個我沒有測試,大家可以試一下,應該沒什么問題。

五、多編譯器的使用:(其下為轉載

為了防止不同編譯器對齊不一樣,建議在代碼里面指定對齊參數

可能重要的一點是關於緊縮結構的。緊縮結構的用途 其實最常用的結構對齊選項就是:默認對齊和緊縮。在兩個程序,或者兩個平台之間傳遞數據時,我們通常會將數據結構設置為緊縮的。這樣不僅可以減小通信量,還可以避免對齊帶來的麻煩。假設甲乙雙方進行跨平台通信,甲方使用了“/Zp2”這么奇怪的對齊選項,而乙方的編譯器不支持這種對齊方式,那么乙方就可以理解什么叫欲哭無淚了。 當我們需要一個字節一個字節訪問結構數據時,我們通常都會希望結構是緊縮的,這樣就不必考慮哪個字節是填充字節了。我們把數據保存到非易失設備時,通常也會采用緊縮結構,既減小存儲量,也方便其它程序讀出。各編譯器都支持結構的緊縮,即連續排列結構的各成員變量,各成員變量之間沒有任何填充字節。這時,結構的大小等於各成員變量大小的和。緊縮結構的變量可以放在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

最后記錄一個小細節。gcc編譯器和VC編譯器都支持在緊縮結構中包含非緊縮結構,例如前面例子中的St2可以包含非緊縮的St1。但對於ARM編譯器而言,緊縮結構包含的其它結構必須是緊縮的。如果緊縮的St2包含了非緊縮的St1,編譯時就會報錯:

 

 

 

 

C語言的字節對齊及#pragma pack的使用

2010-04-16 09:44:33| 分類: vc/c/c++ | 標簽: |字號大中小 訂閱

C編譯器的缺省字節對齊方式(自然對界)

在缺省情況下,C編譯器為每一個變量或是數據單元按其自然對界條件分配空間。

在結構中,編譯器為結構的每個成員按其自然對界(alignment)條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲(成員之間可能有插入的空字節),第一個成員的地址和整個結構的地址相同。

C編譯器缺省的結構成員自然對界條件為“N字節對齊”,N即該成員數據類型的長度。如int型成員的自然對界條件為4字節對齊,而double類型的結構成員的自然對界條件為8字節對齊。若該成員的起始偏移不位於該成員的“默認自然對界條件”上,則在前一個節面后面添加適當個數的空字節。

C編譯器缺省的結構整體的自然對界條件為:該結構所有成員中要求的最大自然對界條件。若結構體各成員長度之和不為“結構整體自然對界條件的整數倍,則在最后一個成員后填充空字節。

例子1(分析結構各成員的默認字節對界條界條件和結構整體的默認字節對界條件):

struct Test
{
char x1; // 成員x1為char型(其起始地址必須1字節對界),其偏移地址為0

char x2; // 成員x2為char型(其起始地址必須1字節對界,其偏移地址為1

float x3; // 成員x3為float型(其起始地址必須4字節對界),編譯器在x2和x3之間填充了兩個空字節,其偏移地址為4

char x4; // 成員x4為char型(其起始地址必須1字節對界),其偏移地址為8
};

因為Test結構體中,最大的成員為flaot x3,因些此結構體的自然對界條件為4字節對齊。則結構體長度就為12字節,內存布局為1100 1111 1000。

例子2:

#include <stdio.h>
//#pragma pack(2)
typedef struct
{
int aa1; //4個字節對齊 1111
char bb1;//1個字節對齊 1
short cc1;//2個字節對齊 011
char dd1; //1個字節對齊 1
} testlength1;
int length1 = sizeof(testlength1); //4個字節對齊,占用字節1111 1011 1000,length = 12

typedef struct
{
char bb2;//1個字節對齊 1
int aa2; //4個字節對齊 01111
short cc2;//2個字節對齊 11
char dd2; //1個字節對齊 1
} testlength2;
int length2 = sizeof(testlength2); //4個字節對齊,占用字節1011 1111 1000,length = 12


typedef struct
{
char bb3; //1個字節對齊 1
char dd3; //1個字節對齊 1
int aa3; //4個字節對齊 001111
short cc23//2個字節對齊 11

} testlength3;
int length3 = sizeof(testlength3); //4個字節對齊,占用字節1100 1111 1100,length = 12


typedef struct
{
char bb4; //1個字節對齊 1
char dd4; //1個字節對齊 1
short cc4;//2個字節對齊 11
int aa4; //4個字節對齊 1111
} testlength4;
int length4 = sizeof(testlength4); //4個字節對齊,占用字節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;
}

改變缺省的對界條件(指定對界)
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節對齊。
· 使用偽指令#pragma pack (),取消自定義字節對齊方式。

這時,對齊規則為:

1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。

2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之后,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。

結合1、2推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。

因此,當使用偽指令#pragma pack (2)時,Test結構體的大小為8,內存布局為11 11 11 10。

需要注意一點,當結構體中包含一個子結構體時,子結構中的成員按照#pragma pack指定的數值和子結構最大數據成員長度中,比較小的那個進行進行對齊。例子如下:

#pragma pack(8)
struct s1{
short a;
long b;
};

struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()

sizeof(s2)的結果為24。S1的內存布局為1100 1111,S2的內存布局為1000 1100 1111 0000 1111 1111。

例子:

#include <stdio.h>
#pragma pack(2)
typedef struct
{
int aa1; //2個字節對齊 1111
char bb1;//1個字節對齊 1
short cc1;//2個字節對齊 011
char dd1; //1個字節對齊 1
} testlength1;
int length1 = sizeof(testlength1); //2個字節對齊,占用字節11 11 10 11 10,length = 10

typedef struct
{
char bb2;//1個字節對齊 1
int aa2; //2個字節對齊 01111
short cc2;//2個字節對齊 11
char dd2; //1個字節對齊 1
} testlength2;
int length2 = sizeof(testlength2); //2個字節對齊,占用字節10 11 11 11 10,length = 10


typedef struct
{
char bb3; //1個字節對齊 1
char dd3; //1個字節對齊 1
int aa3; //2個字節對齊 11 11
short cc23//2個字節對齊 11

} testlength3;
int length3 = sizeof(testlength3); //2個字節對齊,占用字節11 11 11 11,length = 8


typedef struct
{
char bb4; //1個字節對齊 1
char dd4; //1個字節對齊 1
short cc4;//2個字節對齊 11
int aa4; //2個字節對齊 11 11
} testlength4;
int length4 = sizeof(testlength4); //2個字節對齊,占用字節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))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。

· __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際占用字節數進行對齊。

以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM