字對齊之 sizeof和pragma pack 的用法

面試常問的幾道題

1.     sizeof#pragma pack 用法

(注:以下用法都以windows平臺下32位機爲標準。)

sizeof#pragma pack 的用法經常會在面試或筆試中問到,估計很多人也和我一樣,答錯的機率很大。究其原因,還是不明白原理。有時候只是憑着筆試前的記憶亂猜的。

首先,我們看sizeof的用法。

對於結構體:

struct Data1

{

           char  m_ch;

           int   m_nData;

};

sizeof(Data1)=?

a)       可能有人會回答:sizeof(Data1) = 1 + 4=5很明顯,這類人肯定是連字對齊是什麼都不知道。

b)      也有人會這麼回答:sizeof(Data1) = 2 + 4=6這類人可能知道什麼是字對齊,但是不明白到底字對齊是啥意思。可能認爲字對齊就是以2的整數倍對齊。結果面試官一問爲什麼,想當然的就說出了自己的原因,【說32位機器的字對齊方式是這樣的,結構體中每個字段的大小都必須是2的整數倍,因爲這樣CPU尋址會快】,並毫不猶豫的認爲這種說法很完美。但實際上,這只是答對了一點皮毛。很悲劇,我就是屬於這類人,只知道有這麼回事,但並不知道怎麼回事。

經過面試官的指點與自己google出來的結果,我進行了一些總結。我們還是從上面的例子開始講起。這裏先給個鏈接,我當中有很大一部分是根據這個總結而來的。

http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html

n  Windows平臺下,默認的字對齊方式遵循以下2條規則:

條款1    成員變量存放的起始地址相對於結構的起始地址的偏移量必須爲該變量的

型所佔用的字節數的倍數

也就是說,要遵循這麼個法則。

------------------------------------------------------------------------------

成員變量類型 | 對齊方式(變量存放的起始地址相對於結構的起始地址的偏移量)

 

char              | 偏移量必須爲sizeof(char)       1的倍數

short         | 偏移量必須爲sizeof(short)    2的倍數

int                 | 偏移量必須爲sizeof(int)         4的倍數

float              | 偏移量必須爲sizeof(float)     4的倍數

long              | 偏移量必須爲sizeof(long)      4的倍數

double          | 偏移量必須爲sizeof(double), 即8的倍數

__int64          | 偏移量必須爲sizeof(__int64), 即8的倍數

…….

條款2     各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節VC會自動填充。

條款3     整個結構體的大小必須是結構體中所佔空間最大的那個類型所佔用字節數的整數倍。

分析:據這兩條規則,對於結構體Data1,我們做如下分析。

【注意,偏移大小從0開始,也就是說,結構體的第一個字節我們認爲是偏移爲0

首先,編譯器會爲char m_ch分配空間,其起始地址跟結構的起始地址相同,根據條款1,此時相對偏移爲0,正好爲sizeof(char)=1的倍數。然後爲int m_nData分配空間,由於它的相對偏移要是sizeof(int)=4的倍數,顯然,其相對偏移就是4了,那麼,根據條款2,相對偏移爲123的字節就應該空着,裏面什麼內容也不被填充。

再來看條款3滿不滿足,此時結構體的大小已爲8個字節,恰好是結構體裏面最大的數據段【int m_nData】大小的整數倍。

所以,整個數據的大小就爲sizeof(Data1) = 1+3+4=8;

也就是sizeof(Data1) = sizeof(char) + 自動對齊的3個字節 + sizeof(int) = 8;

再來分析一個更加複雜的結構:

struct Data

{

           __int64  m_64n1;

           short    m_sh1;

           __int64  m_64n2;

           short    m_sh2;

           char     m_ch;

           short    m_sh3;

};

第一步,編譯器會爲__int64 m_64n1分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(__int64)=8的整數倍,此時相對偏移爲0【也就是結構體的起始位置】,正好是8的整數倍。此字段佔用的字節爲第0~7個字節。

第二步,編譯器會爲short m_sh1分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(short)=2的整數倍,根據第一步分配後的空間來看,此時相對偏移已經移動到第8個字節,正好是其整數倍。此字段佔用的字節爲第8~9個字節。

第三步,編譯器會爲__int64 m_64n2分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(__int64)=8的整數倍,根據第二步分配後的空間來看,此時相對偏移已經移動到第10個字節,不是其整數倍。那麼此時就要根據條款2,將偏移爲第10~15的位置填充。此時偏移爲16,正好是8的整數倍。此字段佔用的字節爲第16~23個字節。

第四步,編譯器會爲short m_sh2分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(short)=2的整數倍,根據第三步分配後的空間來看,此時相對偏移已經移動到第24個字節,正好是其整數倍。此字段佔用的字節爲第24~25個字節。

第五步,編譯器會爲char m_ch分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(char)=1的整數倍,根據第四步分配後的空間來看,此時相對偏移已經移動到第26個字節,正好是其整數倍。此字段佔用的字節爲第26個字節。

第六步,編譯器會爲short m_sh3分配空間,根據條款1,該成員變量的相對偏移應該是sizeof(short)=2的整數倍,根據第二步分配後的空間來看,此時相對偏移已經移動到第27個字節,不是其整數倍。那麼此時就要根據條款2,將偏移爲第27的位置填充。此時偏移就爲28了,正好是2的整數倍。此字段佔用的字節爲第28~29個字節。

此時,整個結構體大小就爲30,那麼,根據條款3,整個結構體中最大的成員類型(或者是成員變量)的大小爲8,因此,結構體最終大小應該爲32,第30~31個字節自動被填充。

可以定義一個結構體,看一下其內存結構:

Data5 data5 = {1, 2, 3, 4, 5, 6};

01 00 00 00 00 00 00 00 02 00 cc cc cc cc cc cc 03 00 00 00 00 00 00 00 04 00 05 cc 06 00 cc cc

 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  25 26 27 28 29 30 31

上面的一行爲對應偏移。


現在,我們看#pragma pack的用法了。

使用格式:

#pragma pack(push)   // 保存對齊狀態

#pragma pack(4)      // 設定爲4字節對齊

struct Data4

{

    char   m_ch;

    short  m_sh;

    double m_nData;

};

#pragma pack(pop)    // 恢復對齊狀態

這裏我直接把人家的抄過來了,人家寫得很清楚了。

#pragma pack(n)來設定變量以n字節對齊方式。

ü  n字節對齊就是說變量存放的起始地址的偏移量有兩種情況:

第一,  如果n大於等於該變量所佔用的字節數,那麼偏移量必須滿足默認的對齊方式;

第二,  第二,如果n小於該變量的類型所佔用的字節數,那麼偏移量爲n的倍數,不用滿足默認的對齊方式。

ü  結構的總大小也有個約束條件,分下面兩種情況:

如果n大於所有成員變量類型所佔用的字節數,那麼結構的總大小必須爲佔用空間最大的變量佔用的空間數的倍數;

需要注意的一個問題是 n的大小隻能爲爲124816,如果寫其它的,編譯器會報出警告【warning C4086: expected pragma parameter to be '1', '2', '4', '8', or '16'

 

好了,我們來分析一下結構體的大小如何求得。

第一步,編譯器會爲char m_ch分配空間,根據n=4,而sizeof(char)=1,那麼偏移量要滿足默認對齊方式,此時相對偏移爲0,正好是其的整數倍。此字段佔用的字節爲第0個字節。

第二步,編譯器會爲short m_sh分配空間,根據n=4,而sizeof(short)=1,那麼偏移量要滿足默認對齊方式,此時相對偏移爲1,不是其的整數倍。根據先前默認的方式,需要將相對偏移爲第1個字節的位置填充起來,然後再爲該字段分配空間。此字段佔用的字節爲第2~3個字節。

第二步,編譯器會爲double m_nData分配空間,根據n=4,而sizeof(double)=8n<8,那麼偏移量只要是4的倍數就可以了,此時相對偏移爲4,剛好是n的整數倍。所以,此字段佔用的字節爲第4~11個字節。

【再次提醒一下,佔用字段的字節我們按照第0個開始計算,也就是說,如果一個結構體佔用32個字節,那麼我們數數的時候爲第0到第31個字節來算;這裏的偏移也是從0開始計算】

 

還有一些重要的信息我也抄過來了。

sizeof應用在類和結構的處理情況是相同的。但有兩點需要注意,

第一,結構或者類中的靜態成員不對結構或者類的大小產生影響,因爲靜態變量的存儲位置與結構或者類的實例地址無關。

第二,沒有成員變量的結構或類的大小爲1,因爲必須保證結構或類的每一個實例在內存中都有唯一的地址。

參考原文鏈接:http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html

發佈了60 篇原創文章 · 獲贊 32 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章