一、快速理解
1、什么是字節對齊?
在C語言中,結構是一種復合數據類型,其構成元素既可以是基本數據類型(如int、long、float等)的變量,也可以是一些復合數據類型(如數組、結構、聯合等)的數據單元。在結構中,編譯器為結構中的每個成員按其自然邊界(alignment)分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。
為了使CPU能夠對變量進行快速的訪問,變量的起始地址應該具有某些特性,即所謂的“對齊”,比如4字節的int型,其起始地址應該位于4字節的邊界上,即起始地址能夠被4整除。
2、字節對齊有什么作用?
字節對齊的作用不僅是便于CPU的快速訪問,同時合理的利用字節對齊可以有效地節省存儲空間。
對于32位機來說,4字節對齊能夠使CPU訪問速度提高,比如說一個long類型的變量,如果跨越了4字節邊界存儲,那么CPU要讀取兩次,這樣效率就低了。但是在32位機中使用1字節或者2字節對齊,反而會使變量訪問速度降低。所以這要考慮處理器類型,另外還得考慮編譯器的類型。在VC中默認是4字節對齊的,GNU gcc也是默認4字節對齊。
3、更改C編譯器的缺省字節對齊方式
在缺省的情況下,C編譯器為每一個變量或是數據單元按其自然對齊條件分配空間。一般地,可以通過下面的方法改變缺省的對齊條件:
(1)使用偽指令#pragma pack(n),C編譯器將按照n個字節對齊;
(2)使用偽指令#pragma pack(),取消自定義字節對齊方式;
二、字節對齊對程序的影響
先讓我們看看幾個例子吧(32位,x86環境,gcc編譯器):
設結構體如下定義:
- struct A
- {
- int a;
- char b;
- short c;
- };
- struct B
- {
- char b;
- int a;
- short c;
- };
現在已知32位機器上各種數據類型的長度如下:
char: 1(有符號無符號同)
short:2(有符號無符號同)
int:4(有符號無符號同)
long:4(有符號無符號同)
float:4 double:8
那么上面的兩個結構體大小如何呢?
結果是:
sizeof(struct A) = 8
sizeof(struct B) = 12
結構體A中包含了4個字節長度的int一個,1字節長度的char一個核2字節長度的short型數據一個,B也一樣;按理說,A、B大小應該是相同的,都是7個字節。
之所以出現上面的結果是因為編譯器要對數據成員在空間上進行對齊。上面是按照編譯器的默認設置進行對齊的結果,那么我們是不是可以改變編譯器的這種默認設置呢?當然可以,例如:
- #pragma pack(2) //指定按2字節對齊
- struct C
- {
- char b;
- int a;
- short c;
- };
- #pragma pack() //取消指定對齊,恢復缺省對齊
- sizeof(struct C) = 8
- 修改對齊方式為1:
- #pragma pack(1) //指定按1字節對齊
- struct D
- {
- char b;
- int a;
- short c;
- };
- #pragma pack() //取消指定對齊,恢復缺省對齊
- sizeof(struct D) = 7
后面在介紹#pragma pack()的作用。
三、編譯器是按照什么樣的原則進行對齊的
先看四個重要的基本概念:
(1)數據類型自身對齊值
對于char型數據,其自身對齊值為1,對于short型為2,對于int、float、double類型,其自身對齊值為4,單位字節。
(2)結構體或者類的自身對齊值
其成員中自身對齊值最大的那個值。
(3)指定對齊值
#pragma pack(value)時的指定對齊值value。
(4)數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
有了這些值,我們可以很方便的來討論具體數據結構的成員和其自身的對齊方式。有效對齊值N是最終用來決定數據存放地址方式的值,最重要。
有效對齊N,就是表示”對齊在N上“。也就是說該數據的”存放起始地址%N = 0“。而數據結構中的數據變量都是按定義的先后順序來排列的。第一個數據變量的起始地址就是數據結構的其實地址。結構體的成員變量要對齊排列,結構體本身也要根據自身的有效對齊值圓整(就是結構體成員變量占用總長度需要是對結構體有效地址值的整數倍,結合下面例子理解)。這樣就不難理解上面幾個例子的值了。
例子分析,以B為例。
- struct B
- {
- char b;
- int a;
- short c;
- };
假設B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值默認為4。第一個成員變量b的自身對齊值是1,比指定或者默認指定對齊值4小,所以其有效對齊值為1,所以其存放地址0x0000符合0x0000%1=0。第二個成員變量a,其自身對齊值為4,所以有效對齊值也為4,所以只能存放在起始地址為0x0004到0x0007這四個連續的字節空間中,復核0x0004%4=0,且緊靠第一個變量。第三個變量c,自身對齊值為2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個字節空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的都是B內容。再看數據結構B的自身對齊值為其變量中最大對齊值(這里是b)所以就是4,所以結構體的有效對齊值也是4。根據結構體圓整的要求,0x0009到0x0000=10字節,(10+2)%4=0。所以0x0000A到0x000B也為結構體B所占用。故B從0x0000到0x000B共有12個字節,sizeof(struct B)=12;其實如果就這一個就來說它已將滿足字節對齊了,因為它的起始地址是0,因此肯定是對齊的,之所以在后面補充2個字節,是因為編譯器為了實現結構數組的存取效率,試想如果我們定義了一個結構B的數組,那么第一個結構起始地址是0沒有問題,但是第二個結構呢?按照數組的定義,數組中所有元素都是緊挨著的,如果我們不把結構的大小補充為4的整數倍,那么下一個結構的起始地址將是0x0000A,這顯然不能滿足結構的地址對齊了,因此我們要把結構補充成有效對齊大小的整數倍。其實諸如:對于char型數據,其自身對齊值為1,對于short型為2,對于int,float,double類型,其自身對齊值為4,這些已有類型的自身對齊值也是基于數組考慮的,只是因為這些類型的長度已知了,所以他們的自身對齊值也就已知了。
同理,分析上面例子C。
- #pragma pack (2) /*指定按2字節對齊*/
- struct C
- {
- char b;
- int a;
- short c;
- };
- #pragma pack () /*取消指定對齊,恢復缺省對齊*/
第一個變量b的自身對齊值為1,指定對齊值為2,所以,其有效對齊值為1,假設C從0x0000開始,那么b存放在0x0000,符合0x0000%1=0;第二個變量,自身對齊值為4,指定對齊值為2,所以有效對齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續字節中,符合0x0002%2=0。第三個變量c的自身對齊值為2,所以有效對齊值為2,順序存放在0x0006、0x0007中,符合0x0006%2=0。所以從0x0000到0x00007共八字節存放的是C的變量。又C的自身對齊值為4,所以C的有效對齊值為2。又8%2=0,C只占用0x0000到0x0007的八個字節。所以sizeof(struct C)=8。
四、如何修改編譯器的默認對齊值
(1)在VC IDE中,可以修改:【Project】->【Setting】,C/C++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認是8字節;
(2)在編碼時,可以這樣動態修改:#pragma pack。注意:是pragma而不是progma。
五、針對字節對齊,我們在編程中如何考慮
如果在編程的時候要考慮節約空間的話,那么我們只需要假定結構的首地址是0,然后各個變量按照上面的原則進行排列即可,基本的原則就是把結構中的變量按照類型大小從小到大聲明,盡量減少中間的填補空間。還有一種就是為了以空間換取時間的效率,我們顯示的進行填補空間進行對齊。比如:有一種使用空間換時間做法是顯式的插入reserved成員:
- struct A
- {
- char a;
- char reserved[3];//使用空間換時間
- int b;
- };
reversed成員對我們程序沒有什么意義,它只是起到填補空間以達到字節對齊的目的,當然即使不加這個成員,通常編譯器也會給我們自動填補對齊,我們自己加上它只是起到顯式的提醒作用。
六、字節對齊可能帶來的隱患
代碼中關于對齊的隱患,很多是隱式的。比如在強制類型轉換的時候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后兩句代碼,從奇數邊界去訪問unsignedshort型變量,顯然不符合對齊的規定。
在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須字節對齊。
七、如何查找與字節對齊方面的問題
如果出現對齊或者賦值問題,首先查看:
(1)編譯器的big/little端設置;
(2)看這種體系本身是否支持對齊訪問;
(3)如果支持,看設置了對齊與否;如果沒有,則看訪問時需要加某些特殊的修飾來標志其特殊訪問操作;
八、引用
blog.csdn.net/xuegao007/article/details/1708349
![]() |
不含病毒。www.avast.com |