<摘錄>位元組對齊(強制對齊以及自然對齊)

木子你妹發表於2013-06-17

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... 第一種方式較為常見。

相關文章