c語言結構體對齊也是老生常談的話題了。基本上是面試題的必考題。內容雖然很基礎,但一不小心就會弄錯。寫出乙個struct,然後sizeof,你會不會經常對結果感到奇怪?sizeof的結果往往都比你宣告的變數總長度要大,這是怎麼回事呢?
開始學的時候,也被此類問題困擾很久。其實相關的文章很多,感覺說清楚的不多。結構體到底怎樣對齊?
有人給對齊原則做過總結,具體在**看到現在已記不起來,這裡引用一下前人的經驗(在沒有#pragma pack巨集的情況下):
原則1、資料成員對齊規則:結構(struct
或聯合union)的資料成員,第乙個數
據成員放在offset為0的地方,以後每個資料成員儲存的起始位
置要從該成員大小的整數倍開始
(比如int
在32位機為4位元組,則要從4的整數倍位址開始儲存)。
原則2、結構體作為成員:如果乙個結構裡有某些結構體成員,則結構體成員
要從其內部最大元素
大小的整數倍位址開始儲存。
(struct
a裡存有struct
b,b裡有char,int,double等元素,那b應該從8的整數
倍開始儲存。)
原則3、收尾工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊
。這三個原則具體怎樣理解呢?我們看下面幾個例子,通過例項來加深理解。
例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。
考慮乙個問題,為什麼要設計
記憶體對齊
的處理方式呢?如果體系結構是不對齊的,成員將會乙個挨乙個儲存,顯然對齊更浪費了
空間。那麼為什麼要使用對齊呢?體系結構的對齊和不對齊,是在時間和空間上的乙個權衡。對齊節省了時間。假設乙個體繫結構的字長為w,那麼它同時就假設了在這種體系結構上對寬度為w的資料的處理最頻繁也是最重要的。它的設計也是從優先提高對w位資料操作的效率來考慮的。有興趣的可以google一下,人家就可以跟你解釋的,一大堆的道理。
最後順便提一點,在設計結構體的時候,一般會尊照乙個習慣,就是把占用空間小的型別排在前面,占用空間大的型別排在後面,這樣可以相對
節約一些對齊空間。
位域與結構體對齊
一 位域 有些資訊在儲存時,並不需要占用乙個完整的位元組,而只需佔幾個或乙個二進位制位。例如在存放乙個開關量時,只有0和1 兩種狀態,用一位二進位即可。為了節省儲存空間,並使處理簡便,c語言又提供了一種資料結構,稱為 位域 或 位段 所謂 位域 是把乙個位元組中的二進位劃分為幾 個不同的區域,並說明...
位結構體和位域
1.位域的定義 有些資訊在儲存時,並不需要占用乙個完整的位元組,而只需佔幾個或乙個二進位制位。例如在存放乙個開關量時,只有0和1 兩種狀態,用一位二進位即可。為了節省儲存空間,並使處理簡便,c語言又提供了一種資料結構,稱為 位域 或 位段 所謂 位域 是把乙個位元組中的二進位劃分為幾個不同的區域,並...
結構體位元組對齊與位域
code advance struct c includestruct person int main 1.本身的成員變數型別 2.結構體存在位元組對齊 結構體內部最大的單成員型別的整數倍 如果下乙個成員無法在有限的空間存放則需要乙個額外的空間存放 3.如何優化位元組對齊 1.按照有限空間合理排布成...