Author:bakari Date:2012.8.26
數據對齊實際上是內存字節的對齊,今天偶然翻開自己以前做的筆記,發現做了好多的題,但現在對於我來說覺得很陌生。上網查了一下數據對齊的原因和方式,現在把它整理出來以備之后的學習復習鞏固。
轉載請注出處:http://www.cnblogs.com/bakari/archive/2012/08/27/2658956.html
一、什么是數據對齊
1、現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
2、訪問數據的地址要滿足一定的條件,能被這個數據的長度所整除。 例如,1字節數據已經是對齊的,2字節的數據的地址要被2整除,4字節的數據地址要 被4整除。
3、 數據對齊並不是操作系統的內存結構的一部分,而是C P U結構的一部分。
4、 當C P U訪問正確對齊的數據時,它的運行效率最高。當數據大小的數據模數的內存地址是0時,數據是對齊的。例如, W O R D值應該總是從被2除盡的地址開始,而D W O R D值應該總是從被4除盡的地址開始,如此等等。當C P U試圖讀取的數據值沒有正確對齊時, C P U可以執行兩種操作之一。即它可以產生一個異常條件,也可以執行多次對齊的內存訪問,以便讀取完整的未對齊數據值。
二、對齊的原因
1、 現在各個硬件平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的數據只能從某些特定地址開始存取。其他平台可能沒有這種情況, 但是最常見的是如果不按照適合其平台的要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶地址開始,如果一個int型(假設為 32位)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會需要2個讀周期,並對兩次讀出的結果的高低 字節進行拼湊才能得到該int數據。顯然在讀取效率上下降很多。這也是空間和時間的博弈。
2、數據對齊是為了讀取數據的效率。假如說每一次 讀取數據時都是一個字節一個字節讀取,那就不需要對齊了,這跟讀一個字節沒有什 么區別,就是多讀幾次。但是這樣讀取數據效率不高。為了提高讀取數據的帶寬,現 代存儲系統都采用許多並行的存儲芯片來提高讀取效率。
三、數據對齊的實現
通常,我們寫程序的時候,不需要考慮對齊問題。編譯器會替我們選擇適合目標平台的對齊策略。當然,我們也可以通知給編譯器傳遞預編譯指令而改變對指定數據的對齊方法。
1、究竟數據在內存中是如何實現對齊的,對齊的細節以及對齊的方式編譯器是如何展示的?請參考這篇文章,文章中說的很清楚:http://blog.csdn.net/arethe/article/details/2548867,本篇文章主要是通過一些典型的例子進行說明和鞏固。
2、數據對齊的實現方式有兩種,自然對齊(即默認對齊)和強制對齊
1)、自然對齊
一般編譯器如VS2003-VS2010,CB,DEV C++等編譯器的對齊位,默認都是8位,即#pragma pack(value) value = 8。
看一個典型的例子
1 #include <iostream> 2 using namespace std; 3 4 struct A 5 { 6 char _iC1; 7 long _il; 8 char _iC2; 9 double _id; 10 }; 11 //打亂順序 12 struct B 13 { 14 char _iC1; 15 char _iC2; 16 long _il; 17 double _id; 18 }; 19 int main(){ 20 cout << sizeof(A) << endl; 21 cout << sizeof(B) << endl; 22 23 return 0; 24 }
分析:
對於A:_iC1占一個字節,long類型為4個字節,為了讓之后的long類型自然對齊,需要增加3個字節,內存中用cc表示,即增加3cc(下同),_il 占4個字節,_iC2占1個字節,為了讓double對齊,增加7cc,之后_id 占8個字節。所以sizeof(A) = 1+3+4+1+7+8 = 24個字節。
對於B:同理,sizeof(B) = 1+1+2+4+8 = 16
驗證:
總結:
各成員變量存放的地址相對於結構的起始地址的偏移量為sizeof(類型)或其整數倍。結構的總大小是其成員中最大類型的sizeof(該類型)整數倍。所以在定義結構體時最好把結構中的變量按照類型大小從小到大聲明,以減少中間的填補空間。
2)、強制對齊,即人為修改#pragma pack(value) 中value的值
1 #include <iostream> 2 using namespace std; 3 4 #pragma pack(4) //Note! 5 struct A 6 { 7 char _iC1; 8 long _il; 9 char _iC2; 10 double _id; 11 }; 12 #pragma pack() 13 //打亂順序 14 struct B 15 { 16 char _iC1; 17 char _iC2; 18 long _il; 19 double _id; 20 }; 21 int main(){ 22 cout << sizeof(A) << endl; 23 cout << sizeof(B) << endl; 24 25 return 0; 26 }
注意:此處sizeof(A) = 20
分析 :這個地方只在_iC2占有的字節數有所改動,_iC2的有效對齊值不再是double類型的字節數,而是強制對齊值和自身對齊值(即后一個類型的對齊值)的最小值,4 < 8,所以有效對齊值是4.所以:
sizeof(A) = 1+3+4+1+3+8 = 20
對於上面這個有效對齊值的計算有個網友總結得很好,可以參考:
使用#pragma pack(n) 設定對齊系數分為兩種情況:第一、如果n大於等於該成員所占用的字節數,那么偏移量必須滿足默認的對齊方式,即自然對齊方式。第二、如果n小於該成員的類型所占用的字節數,那么偏移量為n的倍數,不用滿足默認的對齊方式。結構的總大小也有個約束條件,分下面兩種情況:如果n大於所有成員變量類型所占用的字節數,那么結構的總大小必須為占用空間最大的變量占用的空間數的倍數; 否則必然為n的倍數。
四、下面通過一些個人認為比較典型的例子進行鞏固
答案在后面,我的答案也不一定正確,可以先自己動手做然后上機驗證后在來參考。
例子1:
struct Practice1
{
char _szA[123];
int _iB;
float _iC;
double _dD;
};
1、 求出此結構體在默認情況下的大小,並將其內存布局通過圖文並茂的方式描述清楚。
2、 若在此結構體上之前加入#pragma pack(2),之后加上#pragma pack(),那么此時,其大小又為多少?
例子2:
struct Practice2
{
struct T1
{
char _cA;
int _iB;
float _fC;
} _objX;
int _iD;
char _iE;
};
1、 求出此結構體在默認情況下的大小,並將其內存布局通過圖文並茂的方式描述清楚。
2、 若在此結構體上之前加入#pragma pack(4),之后加上#pragma pack(),那么此時,其大小又為多少?
例子3:
struct Practice3
{
union T1
{
char _cT;
double _dT;
int _iT;
} _uT;
int _iA;
double _dB;
};
1、 求出此結構體在默認情況下的大小,並將其內存布局通過圖文並茂的方式描述清楚。
2、 若在此結構體上之前加入#pragma pack(1),之后加上#pragma pack(),那么此時,其大小又為多少?
參考答案:
分析:
例子1:
(1)默認對齊值為8,從0x0000開始,即 #pragma pack(8)
char _szA[123]; 123+cc……cc=124
int _iB; 124+4=128
float _iC; 128+4 +cc +...+cc = 132 + 4cc = 136
double _dD; 136+8=144
(2)若#pragma pack(2)
則內存分布為:123+cc+4+4+8=140
例子2:
(1)#pragma pack(8)
struct T1
{
char _cA; 1+cc…+cc=4
int _iB; 4+4=8
float _fC; 8+4=12
} _objX; 12
int _iD; 12+4=16
char _iE; 16+1 = 17+cc+…+cc = 20
所以結果為:20
(2)若#pragma pack(4)
則:1+cc+cc+cc+4+4+4+1+cc+cc+cc=20
例子3:
(1)若#pragma pack(8)
union T1
{
char _cT;
double _dT; 8
int _iT;
} _uT; 8
int _iA; 8+4+cc…+cc=16
double _dB; 16+8 =24
(2)若#pragma pack(1)
則為:8+4+8=20
驗證:
1 #include <iostream> 2 using namespace std; 3 4 struct Practice1 5 { 6 char _szA[123]; 7 int _iA; 8 float _iB; 9 double _iD; 10 }; 11 12 struct Practice2 13 { 14 struct T1 15 { 16 char _cA; 17 int _iB; 18 float _fC; 19 } _objX; 20 int _iD; 21 char _iE; 22 }; 23 24 struct Practice3 25 { 26 union T1 27 { 28 char _cT; 29 double _dT; 30 int _iT; 31 } _uT; 32 int _iA; 33 double _dB; 34 }; 35 36 int main(){ 37 cout << sizeof(Practice1) << endl; 38 cout << sizeof(Practice2) << endl; 39 cout << sizeof(Practice3) << endl; 40 return 0; 41 }
1 #include <iostream> 2 using namespace std; 3 4 #pragma pack(2) 5 struct Practice1 6 { 7 char _szA[123]; 8 int _iA; 9 float _iB; 10 double _iD; 11 }; 12 #pragma pack() 13 14 #pragma pack(4) 15 struct Practice2 16 { 17 struct T1 18 { 19 char _cA; 20 int _iB; 21 float _fC; 22 } _objX; 23 int _iD; 24 char _iE; 25 }; 26 #pragma pack() 27 28 #pragma pack(1) 29 struct Practice3 30 { 31 union T1 32 { 33 char _cT; 34 double _dT; 35 int _iT; 36 } _uT; 37 int _iA; 38 double _dB; 39 }; 40 #pragma pack() 41 42 int main(){ 43 cout << sizeof(Practice1) << endl; 44 cout << sizeof(Practice2) << endl; 45 cout << sizeof(Practice3) << endl; 46 return 0; 47 }
OK,祝君學習愉快!
轉載請注出處:http://www.cnblogs.com/bakari/archive/2012/08/27/2658956.html
我的公眾號 「Linux雲計算網絡」(id: cloud_dev),號內有 10T 書籍和視頻資源,后台回復 「1024」 即可領取,分享的內容包括但不限於 Linux、網絡、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎大家關注。