為什麼要對齊?
現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定型別變數的時候經常在特 定的記憶體位址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。
對齊的作用和原因:各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。比如有些架構的cpu在訪問 乙個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊.其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對 資料存放進行對齊,會在訪問效率上帶來損失。比如有些平台每次讀都是從偶位址開始,如果乙個int型(假設為32位系統)如果存放在偶位址開始的地方,那 麼乙個讀週期就可以讀出這32bit,而如果存放在奇位址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit數 據。顯然在讀取效率上下降很多。
c語言結構體對齊也是老生常談的話題了。基本上是面試題的必考題。結構體到底怎樣對齊?下面總結了對齊原則,在沒有#pragma pack巨集的情況下:
原則1、
普通資料成員對齊規則:第乙個資料成員放在offset為0的地方,以後每個資料成員儲存的起始位置要從該成員大小的整數倍開始(比如int在32位機為4位元組,則要從4的整數倍位址開始儲存)。
原則2、
結構體成員對齊規則:如果乙個結構裡有某些結構體成員,則該結構體成員要從其內部最大元素大小的整數倍位址開始儲存。(struct a裡存有struct b,b裡有char,int,double等元素,那b應該從8的整數倍開始儲存。)
原則3、
結構體大小對齊規則:結構體大小也就是sizeof的結果,必須是其內部成員中最大的對齊引數的整數倍,不足的要補齊。
補充一點,如果陣列作為結構體成員,比如:char a[3]。它的對齊方式和分別寫3個char是一樣的,也就是說它還是按1個位元組對齊。如果寫: typedef char array3[3];array3這種型別的對齊方式還是按1個位元組對齊,而不是按它的長度3對齊。如果共用體作為結構體成員,則該共用體成員要從其內部最大元素大小的整數倍位址開始儲存。
還有一種對齊原則,和上面三條一致,可以參考著理解。
成員對齊有乙個重要的條件,即每個成員按自己的方式對齊。其對齊的規則是,每個成員按其型別的對齊引數(通常是這個型別的大小)和指定對齊引數(這裡預設是8位元組)中較小的乙個對齊。並且結構的長度必須為所用過的所有對齊引數的整數倍,不夠就補空位元組。
這三個原則具體怎樣理解呢?我們看下面幾個例子,通過例項來加深理解。
例1:struct a;
structb;
sizeof(a) = 6; 這個很好理解,三個short都為2。
sizeof(b) = 8; 這個比是不是比預想的大2個位元組?long為4,short為2,整個為8,因為原則3。
例2:struct a;
struct b;
sizeof(a) = 8; int為4,char為1,short為2,這裡用到了原則1和原則3。
sizeof(b) = 12; 是否超出預想範圍?char為1,int為4,short為2,怎麼會是12?還是原則1和原則3。
深究一下,為什麼是這樣,我們可以看看記憶體裡的布局情況。
a b c
a的記憶體布局:1111, 1*,11
b a c
b的記憶體布局:1***,1111,11**
其中星號*表示填充的位元組。a中,b後面為何要補充乙個位元組?因為c為short,其起始位置要為2的倍數,就是原則1。c的後面沒有補充,因為b和c正好占用4個位元組,整個a占用空間為4的倍數,也就是最大成員int型別的倍數,所以不用補充。b中,b是char為1,b後面補充了3個位元組,因為a是int為4,根據原則1,起始位置要為4的倍數,所以b後面要補充3個位元組。c後面補充兩個位元組,根據原則3,整個b占用空間要為4的倍數,c後面不補充,整個b的空間為10,不符,所以要補充2個位元組。
再看乙個結構中含有結構成員的例子:
例3:struct a;
struct b;
sizeof(a) = 24; 這個比較好理解,int為4,double為8,float為4,總長為8的倍數,補齊,所以整個a為24。
sizeof(b) = 48; 看看b的記憶體布局。
e f g h i
b的記憶體布局:11* *,1111,11111111, 11 * * * * * *,1111* * * *, 11111111, 1111 * * * * 。 i其實就是a的記憶體布局。i的起始位置要為24的倍數,所以h後面要補齊。把b的記憶體布局弄清楚,有關結構體的對齊方式基本就算掌握了。
以上講的都是沒有#pragma pack巨集的情況,如果有#pragma pack巨集,對齊方式按照巨集的定義來。比如上面的結構體前加#pragma pack(1),記憶體的布局就會完全改變。sizeof(a) = 16; sizeof(b) = 32;有了#pragma pack(1),記憶體不會再遵循原則1和原則3了,按1位元組對齊。沒錯,這不是理想中的沒有記憶體對齊的世界嗎。
a b c
a的記憶體布局:1111,11111111,1111
e f g h i
b的記憶體布局:11,1111,11111111,11,1111, 11111111, 1111
那#pragma pack(2)的結果又是多少呢?#pragma pack(4)呢?留給大家自己思考吧,相信沒有問題。
還有一種常見的情況,結構體中含位域字段。位域成員不能單獨被取sizeof值。c99規定int、unsigned int和bool可以作為位域型別,但編譯器幾乎都對此作了擴充套件,允許其它型別型別的存在。
使用位域的主要目的是壓縮儲存,其大致規則為:
1) 如果相鄰位域字段的型別相同,且其位寬之和小於型別的sizeof大小,則後面的字段將緊鄰前乙個字段儲存,直到不能容納為止;
2) 如果相鄰位域字段的型別相同,但其位寬之和大於型別的sizeof大小,則後面的字段將從新的儲存單元開始,其偏移量為其型別大小的整數倍;
3) 如果相鄰的位域字段的型別不同,則各編譯器的具體實現有差異,vc6採取不壓縮方式,dev-c++採取壓縮方式;
4) 如果位域字段之間穿插著非位域字段,則不進行壓縮;
5) 整個結構體的總大小為最寬基本型別成員大小的整數倍。
還是讓我們來看看例子。
例4:struct a;
a b c
a的記憶體布局:111,1111 *,11111 * * *
位域型別為char,第1個位元組僅能容納下f1和f2,所以f2被壓縮到第1個位元組中,而f3只能從下乙個位元組開始。因此sizeof(a)的結果為2。
例5:struct b;
由於相鄰位域型別不同,在vc6中其sizeof為6,在dev-c++中為2。
例6:struct c;
非位域字段穿插在其中,不會產生壓縮,在vc6和dev-c++中得到的大小均為3。
在文章結尾我總結一下就是這個對齊就得看你的編譯平台,一般我們的是32位作業系統,我讀取位元組週期大小是4,就是乙個int 我們讀取乙個變數就必須是乙個int型的,如果沒就湊足4個位元組來讀取。舉例如下:
#include
#ifdef true /* 巨集開關,定義這個巨集表示強制對齊 */
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct tagbitmapfileheader bitmapfileheader;
#ifdef true /* 巨集開關,在定義結構體前後都得新增pragma pack()入棧,出棧 */
#pragma pack(pop)
#endif
void main()
關於C語言中的結構體對齊
關於c語言中的結構體對齊 1 什麼是位元組對齊 乙個變數占用 n 個位元組,則該變數的起始位址必須能夠被 n 整除,即 存放起始位址 n 0,對於結構體而言,這個 n 取其成員種的資料型別佔空間的值最大的那個。2 為什麼要位元組對齊 記憶體空間是按照位元組來劃分的,從理論上說對記憶體空間的訪問可以從...
C語言結構體對齊問題詳解
c語言結構體對齊問題詳解 測試環境32位機 winxp 編譯器vc6 ms cl.exe 和 mingw32 gcc 4.5.2 1 結構體資料對齊 沒有 pragma pack 巨集定義 結構體對齊可以總結為三個基本原則 資料成員對齊規則 結構體的資料成員中,第乙個成員從offset為0的位址開始...
C語言結構體對齊問題詳解
測試環境32位機 winxp 編譯器vc6 ms cl.exe 和 mingw32 gcc 4.5.2 1 結構體資料對齊 沒有 pragma pack 巨集定義 結構體對齊可以總結為三個基本原則 資料成員對齊規則 結構體的資料成員中,第乙個成員從offset為0的位址開始,以後每乙個成員儲存的起始...