1. 程式人生 > >C/C++結構體位元組對齊詳解

C/C++結構體位元組對齊詳解

前提:為了訪問速度和效率,需要各種型別資料按照一定的規則在空間上排列;

不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取
某些特定型別的資料,否則丟擲硬體異常。

為了訪問未對⻬的記憶體,處理器需要作兩次記憶體訪問;⽽對⻬的記憶體訪問僅需要
⼀次訪問。

於是有了位元組對齊,4個位元組是一個自然對齊

為什麼是4個位元組?

32位機,即計算機資料匯流排寬度為32個,一次可以處理32位bit(即4個位元組)

不論是結構體(struct)或者聯合(union)在記憶體分佈時都會有位元組對齊,首先資料型別分浮點型,整型,字元型別,

我繪製下面一張表格(針對32位機)

下面是整個測試程式碼(vs2013執行,方便監視),我將分點論述。

typedef struct A
{
	int a;
	double b;
	char c;
}A;


#pragma pack(4)
typedef struct AA
{
	int a;
	double b;
	char c;
}AA;

#pragma pack()


typedef struct B
{
	double b;
	int a;
	char c;
}B;
typedef struct C
{
	char c;
	int a;
	char b;

}C;


typedef struct D
{
	char c;
	char b;
	int a;

}D;


int main()
{
	size_t sizeA = sizeof(A);
	size_t sizeB = sizeof(B);
	size_t sizeC = sizeof(C);
	size_t sizeD = sizeof(D);
	size_t sizeAA = sizeof(AA);

	system("pause");
	return 0;
}

監視結果

 

整個測試中,A和B形成對比,C和D形成對比。

首先, 結構體或類的自身對齊規則:應該對齊   其成員中自身對齊值最大 的那個值。

typedef struct A
{
	int a;
	double b;
	char c;
}A;

結構體A,int佔4位元組,在這個結構體中,應該對齊double型別,所以佔據空間8個位元組(後4個空置),double佔8位元組,char型別情況同int型別  最終  佔 8+8+8=24位元組。

typedef struct B
{
	double b;
	int a;
	char c;
}B;

結構體B內成員和A一樣,更換順序後,double佔8位元組,int應該和double對齊,提取8個位元組空間,空餘4個位元組空間。但是char型別只佔1個位元組,可以利用int後空餘空間,佔據1位元組

最終結構體B大小為8+8=16

typedef struct C
{
	char c;
	int a;
	char b;

}C;

結構體C ,最大為int 對齊空間為4個位元組,char c 佔1個位元組,後面3個位元組的空缺,int佔4個位元組, char b同char c,最終4+4+4=12位元組


typedef struct D
{
	char c;
	char b;
	int a;

}D;

結構體D,是對C的優化,同例子二B對A的優化,char c和char b共同佔據4個位元組,int佔據4個位元組,4+4=8位元組

對其擴充套件

typedef struct E
{
	char c;
	char b;
	char f;
	char g;
	int a;

}E;

 結構體E也只佔據8個位元組;

接下來我說一下program pack。

#pragma pack(4)
typedef struct AA
{
	int a;
	double b;
	char c;
}AA;

#pragma pack()

結構體AA和結構體A的內容和順序都是一樣的,但是AA的大小為16個。

這是由於 #pragma pack(n)預編譯時,每一次儲存,成員的大小都會和n來對比,成員資料型別對齊數  小於n,則預設編譯,若大於或等於(>=)n,則按n位元組儲存(不合理的n有時會被編譯器優化,linux則會默許程式設計師的預編譯處理)。

AA中 自身對齊數為8位元組,大於4 ,int儲存在4個位元組大小,double本身為8位元組,無法改變,儲存8個位元組,c佔1個位元組,儲存pack的n位元組數,佔4個位元組,最終 4+8+4=16位元組

當然,pack可以設定為pack(1),最終只佔13位元組,但是我們的機器是32位機器,在保證空間的同時也要保證效率。

下面是對本文的濃縮概括。

一個字或雙字運算元跨越了 4 位元組邊界,或者一個四字運算元跨越了 8 位元組邊界,被認為是未對齊的,從而需要兩次匯流排週期來訪問記憶體。

一個字起始地址是奇數但卻沒有跨越字邊界被認為是對齊的,能夠在一個匯流排週期中被訪問。

某些操作雙四字的指令需要記憶體運算元在自然邊界上對齊。如果運算元沒有對齊,這些指令將會產生一個通用保護異常。

雙四字的自然邊界是能夠被 16 整除的地址。其他的操作雙四字的指令允許未對齊的訪問(不會產生通用保護異常),然而,需要額外的記憶體匯流排週期來訪問記憶體中未對齊的資料。

預設情況下,編譯器預設將結構、棧中的成員資料進行記憶體對齊。