現(xiàn)在的一些處理器,需要你的數(shù)據(jù)的內(nèi)存地址必須是對齊(align)的,即使不是必須,如果你對齊的話,運(yùn)行的速度也會得到提升。雖然對齊會產(chǎn)生的額外內(nèi)存空間,但相對于這個速度的提升來說,是值得的。
所謂對齊,就是地址必須能整除一個整數(shù),這個就是對齊參數(shù)(alignment value)。合法的取值范圍是1、2、4、6、16、……、8192。
怎樣對齊呢?編譯器幫你搞定。
怎樣設(shè)置編譯器的對齊方式呢?用#pragma pack( n )和__declspec(align(#))。
依據(jù)它倆,編譯器是咋工作的?這個就是接下來要說的了。
#include <stdio.h>
#pragma pack( 1 )
struct A
{
char a;
short b;
char c;
};
int main()
{
printf("%d\n",sizeof(A));
return 0;
}
OK,下面對這個代碼進(jìn)行詳細(xì)的分析。
用MSDN的話一言以蔽之:
“The alignment of a member (except the first one) will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.”
翻譯成中文,也就是:
“結(jié)構(gòu)體中的數(shù)據(jù)成員,除了第一個是始終放在最開始的地方,其它數(shù)據(jù)成員的地址必須是它本身大小或?qū)R參數(shù)兩者中較小的一個的倍數(shù)。”
P.S:注意上面所說的后面一句話,也就是說,結(jié)構(gòu)體的數(shù)據(jù)成員的地址必須是本身大小和對齊參數(shù)中較小的那一個。
(1)在pack為1的時候,對齊參數(shù)是1,那么我們對這個結(jié)構(gòu)體每一元素進(jìn)行分析。
char a; // 第一個元素在[0]位置處
short b; //short兩個字節(jié),地址是min(1,sizeof(short))的倍數(shù),即1的倍數(shù)[1~2]
char c; // 地址應(yīng)該是min(1,sizeof(1))的倍數(shù),從而即為[3]
故在pack為1的時候,輸出的結(jié)果應(yīng)該是4([0~3]),其中所有的元素都填滿了。
(2)在pack為2的時候,同樣按照上面的方法,我們繼續(xù)來分析下。
Char a; //第一個占[0]位置。
Short b; //min(2,sizeof(short)),也就是必須為2的倍數(shù),從而[2~3]
Char c;//min(2,sizeof(char)),也就是位1,地址為[4]
因此最后占據(jù)的大小是[0],[2~3],[4],整個結(jié)構(gòu)體的大小size必須是2的倍數(shù),所以應(yīng)該是6(向上對齊至2的倍數(shù))
(3)在pack為4的時候,同上,得到的結(jié)果是
[0],[2~3],[4],因此也是6.
然后我們對上面的這個結(jié)構(gòu)體變換一下順序,可以得到。
struct B
{
char a;
char b;
short c;
};
在#pragma pack(4)的情況下,輸出卻是4(注:上面的輸出時6)
解釋如下:
Char a;//占據(jù)一個字節(jié),地址為【0】
Char b;//地址應(yīng)該是min(4,sizeof(char)) = 1的倍數(shù),也就是地址為【1】
Short c; //地址應(yīng)該是min(4,sizeof(short)) = 2的倍數(shù),也就是【2~3】
故總體占據(jù)的是【0~3】的連續(xù)單元,也就是4.
至此,我們對#prgama pack(n)的用法和對應(yīng)的判定方法有了一個全新的認(rèn)識。
特別提出:
sizeof(ao.a )還是1,sizeof(ao.b )還是2。
如果struct B中含有A的一個對象m_a,
struct B
{
…
A m_a;
…
}
則這個m_a對齊參數(shù)是A中最大的數(shù)據(jù)類型的大小(這里是short的2)和n中較小者。如果這個對齊參數(shù)是B中最大的話,最后B的大小也會與這個對齊參數(shù)有關(guān)。
m_a的對齊參數(shù),由于是A的變量,所以采用A的對齊參數(shù),也就是應(yīng)該是A的最大元素個數(shù)和n中較小的值。而B的大小就要根據(jù)這個對齊參數(shù)來確定大小。
#include <iostream>
#include <stdlib.h>
#define NUM 1
using namespace std;
#pragma pack ( 16 )
typedef struct {
int a;
char b;
double c;
}test;
struct B
{
int a;
test b;
};
int main()
{
cout << "sizeof(int) = "<<sizeof(int) << endl;
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(test)<< endl;
cout << sizeof(B) << endl;
system("PAUSE");
return 0;
}
(1)在pack為1的時候,由于min中有一個為1,所以都是相鄰存放的。
Sizeof(test)就是int+char+double的大小之和,即13.
而對應(yīng)的sizeof(B)則是一個int和一個struct之和。Int占4B,而struct的對齊參數(shù)則是
Min(1,sizeof(max(A)),A中最大的元素師double類型的,也就是8,所以結(jié)果是min(1,8)=1,所以也是相鄰存放的,而sizeof(A)的結(jié)果是13,所以直接是13+4 = 17.
此時,sizeof(B)的大小是17.
(2) 在pack為2的時候,此時min中有一個為2,對于test結(jié)構(gòu)體,它的大小是4+2+8=14,因?yàn)樵赿ouble的時候,min(2,8)=2,所以double類型的變量應(yīng)該是2的倍數(shù)的地址,造成了char類型處空出了一個字節(jié)。總體就是14B。而對于B結(jié)構(gòu)體而言,一個int占據(jù)4B,然后結(jié)構(gòu)體的對齊參數(shù)采用min(2,max(A)),即min(2,8)= 2,由于是int,所以下一個地址是4,自然也是2的倍數(shù),于是還是相鄰存放。而A結(jié)構(gòu)體的大小時14,于是B結(jié)構(gòu)體的大小時14+4=18.
(3) 在pack為4的情況下。同樣可以得到。此時對于A結(jié)構(gòu)體的大小是4+4+8=16,因?yàn)閐ouble類型的必須是4的倍數(shù),造成了char變量要占4個地方(實(shí)際只占一個,只是說這個地方空出來了3B),所以總體的大小為16.而同樣對于B結(jié)構(gòu)體,sizeof的結(jié)果是16+4 = 20,因?yàn)閷τ诶锩娴某蓡T要是min(4,8) = 4,而int恰好是4的倍數(shù),所以相鄰存放。于是就是16,20.
(4) 在pack為8的情況下(有所變化!!!),此時A結(jié)構(gòu)體的大小是16,分析方法和上面相同,但是對于結(jié)構(gòu)體B而言就有所區(qū)別,此時int還是4個字節(jié),但是對于成員test結(jié)構(gòu)體,它的對齊參數(shù)是min(8,max(A)) = min(8,sizeof(double) ) = 8也就是對齊參數(shù)是8,所以結(jié)構(gòu)體變量要從地址為8開始,此時int就空出來了4B,從而最后求大小的時候應(yīng)該是8+sizeof(A)= 8+16=24(最終測試結(jié)果如此)
(5)在pack為16的情況(以及以后的情況),結(jié)果是:A的大小為16B,而B的大小是24B。
總結(jié):
(1) 對于一個由簡單類型組成的結(jié)構(gòu)體,它的大小是由每一個成員變量的地址決定的。我們要按照定義的順序,分別求出來地址開始的地方。從地址為0開始,每一個變量都采取min(n,sizeof(x))//x表示該變量的類型;來確定起始地址是多少的倍數(shù),然后開始計(jì)數(shù),直到填滿該數(shù)據(jù)。最后求出來總的大小。而且在pack>=2的時候最終的大小需要時2的倍數(shù),有時候需要向上取大為2的倍數(shù)。而在pack為1的情況則不需要。
(2) 對于含有結(jié)構(gòu)體成員的結(jié)構(gòu)體,方法同上,只是在于對于結(jié)構(gòu)體變量的對齊參數(shù)取法需要說明,具體就是min(n,結(jié)構(gòu)體成員的最大元素的大小),就像上面的,結(jié)構(gòu)體B中含有A成員,所以對齊參數(shù)就是min(n,sizeof(double))的大小,然后按照這個做法來取地址。
P.S:注意這里是pack而不是package,否則編譯器會直接忽略#pragma package(),因?yàn)榧词拱l(fā)生錯誤編譯器也會直接忽略,而我們還是會默認(rèn)認(rèn)為編譯器已經(jīng)當(dāng)做了字節(jié)按照n來處理。(某些博客上面的內(nèi)容很容易讓人誤解或者暈倒!)
以上代碼結(jié)果在Dev C++ , C-Free 5.0,VS 2010上均通過測試。
參考資料:
http://blog.csdn.net/whoismickey/archive/2009/03/28/4032155.aspx
posted on 2011-03-13 14:09
deercoder 閱讀(5136)
評論(2) 編輯 收藏 引用 所屬分類:
C++