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
驗證:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pn5Gcu4WNHNWdRR1Tw0EVOpXWU10MJR0T3lEVNdXS6xEeJRlT41EROZXSU10dJpHT6ZlMahWMXFmdwIjYqVTej5WOHJWa1ITW1lERNdXTXF2d5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
總結:
各成員變量存放的位址相對于結構的起始位址的偏移量為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++程式設計技術等内容,歡迎大家關注。