C語(yǔ)言的那些小秘密之字節對齊
可能有不少讀者會(huì )問(wèn),字節對齊有必要拿出來(lái)單獨寫(xiě)一篇博客嘛?我覺(jué)得是很有必要,但是它卻是被很多人所忽視的一個(gè)重點(diǎn)。那么我們使用字節對齊的作用和原因是什么呢?由于硬件平臺之間對存儲空間的處理上是有很大不同的,一些平臺對某些特定類(lèi)型的數據只能從某些特定地址開(kāi)始存取,如通常有些架構的CPU要求在編程時(shí)必須保證字節對齊,否則訪(fǎng)問(wèn)一個(gè)沒(méi)有進(jìn)行字節對齊的變量的時(shí)候會(huì )發(fā)生錯誤。而有些平臺可能沒(méi)有這種情況,但是通常的情況是如果我們編程的時(shí)候不按照適合其平臺要求對數據存放進(jìn)行對齊,會(huì )在存取效率上帶來(lái)?yè)p失。比如有些平臺每次讀都是從偶地址開(kāi)始,如我們操作一個(gè)int型數據,如果存放在偶地址開(kāi)始的地方,那么一個(gè)讀周期就可以讀出,而如果存放在奇地址開(kāi)始的地方,就可能會(huì )需要2個(gè)讀周期,兩個(gè)周期讀取出來(lái)的字節我們還要對它們進(jìn)行高低字節的拼湊才能得到該int型數據,從而使得我們的讀取效率較低,這也從側面反映出了一個(gè)問(wèn)題,就是我們很多時(shí)候是在犧牲空間來(lái)節省時(shí)間的。
本文引用地址:http://dyxdggzs.com/article/272734.htm可能看了上面的講解你還是不太明白,那我們再來(lái)看一次什么是字節對齊呢? 我們現在的計算機中內存空間都是按照字節來(lái)進(jìn)行劃分的,從理論上來(lái)講的話(huà)似乎對任何類(lèi)型的變量的訪(fǎng)問(wèn)可以從任何地址開(kāi)始,然而值得注意的就是,實(shí)際情況下在訪(fǎng)問(wèn)特定變量的時(shí)候經(jīng)常在特定的內存地址訪(fǎng)問(wèn),從而就需要各種類(lèi)型的數據按照一定的規則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對齊。
按照預先的計劃安排,這次應該是寫(xiě)《C語(yǔ)言的那些小秘密之鏈表(三)》的,但是我發(fā)現如果直接開(kāi)始講解linux內核鏈表的話(huà),可能有些地方如果我們不在此做一個(gè)適當的講解的話(huà),有的讀者看起來(lái)可能難以理解,所以就把字節對齊挑出來(lái)另寫(xiě)一篇博客,我在此盡可能的講解完關(guān)于字節對齊的內容,希望我的講解對你有所幫助。
在此之前我們不得不提的一個(gè)操作符就是sizeof,其作用就是返回一個(gè)對象或者類(lèi)型所占的內存字節數。我們?yōu)槭裁床辉诖朔Q(chēng)之為sizeof()函數呢?看看下面一段代碼:
[html] view plaincopy#include
void print()
{
printf("hello world!n");
return ;
}
void main()
{
printf("%dn",sizeof(print()));
return ;
}
這段代碼在linux環(huán)境下我采用gcc編譯是沒(méi)有任何問(wèn)題的,對于void類(lèi)型,其長(cháng)度為1,但是如果我們在vc6下面運行的話(huà)話(huà)就會(huì )出現illegal sizeof operand錯誤,所以我們稱(chēng)之為操作符更加的準確些,既然是操作符,那么我們來(lái)看看它的幾種使用方式:
1、sizeof( object ); // sizeof( 對象 );
2、 sizeof( type_name ); // sizeof( 類(lèi)型 );
3、sizeof object; // sizeof 對象; 通常這種寫(xiě)法我們在代碼中都不會(huì )使用,所以很少見(jiàn)到。
下面來(lái)看段代碼加深下印象:
[html] view plaincopy#include
void main()
{
int i;
printf("sizeof(i):t%dn",sizeof(i));
printf("sizeof(4):t%dn",sizeof(4));
printf("sizeof(4+2.5):t%dn",sizeof(4+2.5));
printf("sizeof(int):t%dn",sizeof(int));
printf("sizeof 5:t%dn",sizeof 5);
return ;
}
運行結果為:

[html] view plaincopysizeof(i): 4
sizeof(4): 4
sizeof(4+2.5): 8
sizeof(int): 4
sizeof 5: 4
Press any key to continue
從運行結果我們可以看出上面的幾種使用方式,實(shí)際上,sizeof計算對象的大小也是轉換成對對象類(lèi)型的計算,也就是說(shuō),同種類(lèi)型的不同對象其sizeof值都是一樣的。從給出的代碼中我們也可以看出sizeof可以對一個(gè)表達式求值,編譯器根據表達式的最終結果類(lèi)型來(lái)確定大小,但是一般不會(huì )對表達式進(jìn)行計算或者當表達式為函數時(shí)并不執行函數體。如:
[html] view plaincopy#include
int print()
{
printf("Hello bigloomy!");
return 0;
}
void main()
{
printf("sizeof(print()):t%dn",sizeof(print()));
return ;
}
運行結果為:

[html] view plaincopysizeof(print()): 4
Press any key to continue
從結果我們可以看出print()函數并沒(méi)有被調用。
接下來(lái)我們來(lái)看看linux內核鏈表里的一個(gè)宏:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
對這個(gè)宏的講解我們大致可以分為以下4步進(jìn)行講解:
1、( (TYPE *)0 ) 0地址強制 "轉換" 為 TYPE結構類(lèi)型的指針;
2、((TYPE *)0)->MEMBER 訪(fǎng)問(wèn)TYPE結構中的MEMBER數據成員;
3、&( ( (TYPE *)0 )->MEMBER)取出TYPE結構中的數據成員MEMBER的地址;
4、(size_t)(&(((TYPE*)0)->MEMBER))結果轉換為size_t類(lèi)型。
宏offsetof的巧妙之處在于將0地址強制轉換為 TYPE結構類(lèi)型的指針,TYPE結構以?xún)却婵臻g首地址0作為起始地址,則成員地址自然為偏移地址??赡苡械淖x者會(huì )想是不是非要用0呢?當然不是,我們僅僅是為了計算的簡(jiǎn)便。也可以使用是他的值,只是算出來(lái)的結果還要再減去該數值才是偏移地址。來(lái)看看下面的代碼:
[cpp] view plaincopy#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)
typedef struct stu1
{
int a;
int b;
}stu1;
void main()
{
printf("offsetof(stu1,a):t%dn",offsetof(stu1,a)-4);
printf("offsetof(stu1,b):t%dn",offsetof(stu1,b)-4);
}
運行結果為:

[cpp] view plaincopyoffsetof(stu1,a): 0
offsetof(stu1,b): 4
Press any key to continue
為了讓讀者加深印象,我們這里在代碼中沒(méi)有使用0,而是使用的4,所以在最終計算出的結果部分減去了一個(gè)4才是偏移地址,當然實(shí)際使用中我們都是用的是0。
懂了上面的宏offsetof之后我們再來(lái)看看下面的代碼:
[cpp] view plaincopy#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct stu1
{
int a;
char b[1];
int c;
}stu1;
void main()
{
printf("offsetof(stu1,a):t%dn",offsetof(stu1,a));
printf("offsetof(stu1,b):t%dn",offsetof(stu1,b));
printf("offsetof(stu1,c):t%dn",offsetof(stu1,c));
printf("sizeof(stu1) :t%dn",sizeof(stu1));
}
運行結果為:

[cpp] view plaincopyoffsetof(stu1,a): 0
offsetof(stu1,b): 4
offsetof(stu1,c): 8
sizeof(stu1) : 12
Press any key to continue
對于字節對齊不了解的讀者可能有疑惑的是c的偏移量怎么會(huì )是8和結構體的大小怎么會(huì )是12呢?因該是sizeof(int)+sizeof(char)+sizeof(int)=9。其實(shí)這是編譯器對變量存儲的一個(gè)特殊處理。為了提高CPU的存儲速度,編譯器對一些變量的起始地址做了對齊處理。在默認情況下,編譯器規定各成員變量存放的起始地址相對于結構的起始地址的偏移量必須為該變量的類(lèi)型所占用的字節數的倍數?,F在來(lái)分析下上面的代碼,如果我們假定a的起始地址為0,它占用了4個(gè)字節,那么接下來(lái)的空閑地址就是4,是1的倍數,滿(mǎn)足要求,所以b存放的起始地址是4,占用一個(gè)字節。接下來(lái)的空閑地址為5,而c是int變量,占用4個(gè)字節,5不是4的整數倍,所以向后移動(dòng),找到離5最近的8作為存放c的起始地址,c也占用4字節,所以最后使得結構體的大小為12?,F在我們再來(lái)看看下面的代碼:
[cpp] view plaincopy#include
typedef struct stu1
{
char array[7];
}stu1;
typedef struct stu2
{
double fa;
}stu2;
typedef struct stu3
{
stu1 s;
char str;
}stu3;
typedef struct stu4
{
stu2 s;
char str;
}stu4;
void main()
{
printf("sizeof(stu1) :t%dn",sizeof(stu1));
printf("sizeof(stu2) :t%dn",sizeof(stu2));
printf("sizeof(stu3) :t%dn",sizeof(stu3));
printf("sizeof(stu4) :t%dn",sizeof(stu4));
}
運行結果為:

[cpp] view plaincopysizeof(stu1) : 7
sizeof(stu2) : 8
sizeof(stu3) : 8
sizeof(stu4) : 16
Press any key to continue
分析下上面我們的運行結果,重點(diǎn)是struct stu3和struct stu4,在struct stu3中使用的是一個(gè)字節對齊,因為在stu1和stu3中都只有一個(gè)char類(lèi)型,在struct stu3中我們定義了一個(gè)stu1類(lèi)型的 s,而stu1所占的大小為7,所以加上加上接下來(lái)的一個(gè)字節str,sizeof(stu3)為8。在stu4中,由于我們定義了一個(gè)stu2類(lèi)型的s,而s是一個(gè)double類(lèi)型的變量,占用8字節,所以接下來(lái)在stu4中采用的是8字節對齊。如果我們此時(shí)假定stu4中的s從地址0開(kāi)始存放,占用8個(gè)字節,接下來(lái)的空閑地址就是8,根據我們上面的講解可知剛好可以在此存放str。所以變量都分配完空間后stu4結構體所占的字節數為9,但9不是結構體的邊界數,也就是說(shuō)我們要求分配的字節數為結構體中占用空間最大的類(lèi)型所占用的字節數的整數倍,在這里也就是double類(lèi)型所占用的字節數8的整數倍,所以接下來(lái)還要再分配7個(gè)字節的空間,該7個(gè)字節的空間沒(méi)有使用,由編譯器自動(dòng)填充,沒(méi)有存放任何有意義的東西。
當然我們也可以使用預編譯指令#pragma pack (value)來(lái)告訴編譯器,使用我們指定的對齊值來(lái)取代缺省的。接下來(lái)我們來(lái)看看一段代碼。
[cpp] view plaincopy#include
#pragma pack (1) /*指定按1字節對齊*/
typedef union stu1
{
char str[10];
int b;
}stu1;
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
typedef union stu2
{
char str[10];
int b;
}stu2;
void main()
{
printf("sizeof(stu1) :t%dn",sizeof(stu1));
printf("sizeof(stu2) :t%dn",sizeof(stu2));
}
運行結果為:

[cpp] view plaincopysizeof(stu1) : 10
sizeof(stu2) : 12
Press any key to continue
現在來(lái)分析下上面的代碼。由于之前我們一直都在使用struct,所以在這里我們特地例舉了一個(gè)union的代碼來(lái)分析下,我們大家都知道union的大小取決于它所有的成員中占用空間最大的一個(gè)成員的大小。由于在union stu1中我們使用了1字節對齊,所以對于stu1來(lái)說(shuō)占用空間最大的是char str[10]類(lèi)型的數組,,其值為10。為什么stu1為10而stu2卻是12呢?因為在stu2的上面我們使用了#pragma pack () ,取消指定對齊,恢復缺省對齊。所以由于stu2其中int類(lèi)型成員的存在,使stu2的對齊方式變成4字節對齊,也就是說(shuō),stu2的大小必須在4的對界上,換句話(huà)說(shuō)就是stu2的大小要是4的整數倍,所以占用的空間變成了12。
linux操作系統文章專(zhuān)題:linux操作系統詳解(linux不再難懂)
c語(yǔ)言相關(guān)文章:c語(yǔ)言教程
linux相關(guān)文章:linux教程
評論