C語言結構體問題

2021-05-26 20:35:59 字數 3253 閱讀 2295

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一下,人家就可以跟你解釋的,一大堆的道理。

最後順便提一點,在設計結構體的時候,一般會尊照乙個習慣,就是把占用空間小的型別排在前面,占用空間大的型別排在後面,這樣可以相對節約一些對齊空間。

c語言結構體賦值問題

對於結構體賦值問題 1 static psl 我過去一般會對結構體這樣賦值 static psl pslname 記住有一點,b 後面的逗號可有可無,至於具體為什麼,我只能理解自己能力還不夠。現在只知道可有可無。上面這種賦值方式是對的。看看下面的一種賦值方式 static psl pslname 這...

C語言小問題 結構體

宣告 c語言小問題系列 基本均來自 你必須知道的495個c語言問題 一書 絕非原創哈 寫下來 純粹為了學習 也希望有更多的人可以看到 看下面的兩個宣告 有什麼區別 struct x1 typedef struct x2 第一種形式其實宣告了乙個結構標籤 structure tag 第二種宣告了乙個 ...

C語言結構體對齊問題

結構體對齊規則 預設對齊方式,按結構體的成員中 size 最大的成員對齊。拋開成員size來說,一般情況下32位機器預設4位元組對齊,64位機器預設8位元組對齊。另外可以使用偽指令 pragma pack n 修改預設的位元組對齊。1.使用偽指令 pragma pack n 編譯器將按照 n 個位元...