由sizeof求結構體大小時涉及到的資料對齊

2021-07-15 10:28:02 字數 4114 閱讀 9442

結構體位元組對齊

在用sizeof運算子求算某結構體所佔空間時,並不是簡單地將結構體中所有元素各自佔的空間相加,這裡涉及到記憶體位元組對齊的問題。從理論上講,對於任何變數的訪問都可以從任何位址開始訪問,但是事實上不是如此,實際上訪問特定型別的變數只能在特定的位址訪問,這就需要各個變數在空間上按一定的規則排列,而不是簡單地順序排列,這就是記憶體對齊。

記憶體對齊的原因:

1)某些平台只能在特定的位址處訪問特定型別的資料;

2)提高訪問資料的速度。比如有的平台每次都是從偶位址處讀取資料,對於乙個int型的變數,若從偶位址單元處存放,則只需乙個讀取週期即可讀取該變數;但是若從奇位址單元處存放,則需要2個讀取週期讀取該變數。

在c99標準中,對於記憶體對齊的細節沒有作過多的描述,具體的實現交由編譯器去處理,所以在不同的編譯環境下,記憶體對齊可能略有不同,但是對齊的最基本原則是一致的,對於結構體的位元組對齊主要有下面兩點:

1)結構體每個成員相對結構體首位址的偏移量(offset)是對齊引數的整數倍,如有需要會在成員之間填充位元組。編譯器在為結構體成員開闢空間時,首先檢查預開闢空間的位址相對於結構體首位址的偏移量是否為對齊引數的整數倍,若是,則存放該成員;若不是,則填充若干位元組,以達到整數倍的要求。

2)結構體變數所佔空間的大小是對齊引數大小的整數倍。如有需要會在最後乙個成員末尾填充若干位元組使得所佔空間大小是對齊引數大小的整數倍。

注意:在看這兩條原則之前,先了解一下對齊引數這個概念。對於每個變數,它自身有對齊引數,這個自身對齊引數在不同編譯環境下不同。下面列舉的是兩種最常見的編譯環境下各種型別變數的自身對齊引數

從上面可以發現,在windows(32)/vc6.0下各種型別的變數的自身對齊引數就是該型別變數所佔位元組數的大小,而在linux(32)/gcc下double型別的變數自身對齊引數是4,是因為linux(32)/gcc下如果該型別變數的長度沒有超過cpu的字長,則以該型別變數的長度作為自身對齊引數,如果該型別變數的長度超過cpu字長,則自身對齊引數為cpu字長,而32位系統其cpu字長是4,所以linux(32)/gcc下double型別的變數自身對齊引數是4,如果是在linux(64)下,則double型別的自身對齊引數是8。

除了變數的自身對齊引數外,還有乙個對齊引數,就是每個編譯器預設的對齊引數#pragma pack(n),這個值可以通過**去設定,如果沒有設定,則取系統的預設值。在windows(32)/vc6.0下,n的取值可以為1、2、4、8,預設情況下為8。在linux(32)/gcc下,n的取值只能為1、2、4,預設情況下為4。注意像dev-cpp、mingw等在windows下n的取值和vc的相同。

了解了這2個概念之後,可以理解上面2條原則了。對於第一條原則,每個變數相對於結構體的首位址的偏移量必須是對齊引數的整數倍,這句話中的對齊引數是取每個變數自身對齊引數和系統預設對齊引數#pragma pack(n)中較小的乙個。舉個簡單的例子,比如在結構體a中有變數int a,a的自身對齊引數為4(環境為windows/vc),而vc預設的對齊引數為8,取較小者,則對於a,它相對於結構體a的起始位址的偏移量必須是4的倍數。

對於第二條原則,結構體變數所佔空間的大小是對齊引數的整數倍。這句話中的對齊引數有點複雜,它是取結構體中所有變數的對齊引數的最大值和系統預設對齊引數#pragma pack(n)比較,較小者作為對齊引數。舉個例子假如在結構體a中先後定義了兩個變數int a;double b;對於變數a,它的自身對齊引數為4,而#pragma pack(n)值預設為8,則a的對齊引數為4;b的自身對齊引數為8,而#pragma pack(n)的預設值為8,則b的對齊引數為8。由於a的最終對齊引數為4,b的最終對齊引數為8,那麼兩者較大者是8,然後再拿8和#pragma pack(n)作比較,取較小者作為對齊引數,也就是8,即意味著結構體最終的大小必須能被8整除。

下面是測試例子:

注意:以下例子的測試結果均在windows(32)/vc下測試的,其預設對齊引數為8

//#pragma pack(4)    //設定4位元組對齊 

//#pragma pack() //取消4位元組對齊

typedef struct node1

s1;typedef struct node2

s2;typedef struct node3

s3;typedef struct node4

s4;typedef struct node5

s5;int main(int argc, char *argv)

s2;

sizeof(s2)=12;

對於變數a,它的自身對齊引數為1,#pragma pack(n)預設值為8,則最終a的對齊引數為1,為其分配1位元組的空間,它相對於結構體起始位址的偏移量為0,能被4整除;

對於變數b,它的自身對齊引數為4,#pragma pack(n)預設值為8,則最終b的對齊引數為4,接下來的位址相對於結構體的起始位址的偏移量為1,1不能夠整除4,所以需要在a後面填充3位元組使得偏移量達到4,然後再為b分配4位元組的空間;

對於變數c,它的自身對齊引數為2,#pragma pack(n)預設值為8,則最終c的對齊引數為2,而接下來的位址相對於結構體的起始位址的偏移量為8,能整除2,所以直接為c分配2位元組的空間。

此時結構體所佔的位元組數為1+3+4+2=10位元組

最後由於a,b,c的最終對齊引數分別為1,4,2,最大為4,#pragma pack(n)的預設值為8,則結構體變數最後的大小必須能被4整除。而10不能夠整除4,所以需要在後面填充2位元組達到12位元組。其儲存如下:

|char|----|----|----|  4位元組

|--------int--------|  4位元組

|--short--|----|----|  4位元組

總共佔12個位元組

對於node3,含有靜態資料成員 

typedef struct

node3

s3;

則sizeof(s3)=8.這裡結構體中包含靜態資料成員,而靜態資料成員的存放位置與結構體例項的儲存位址無關(注意只有在c++中結構體中才能含有靜態資料成員,而c中結構體中是不允許含有靜態資料成員的)。其在記憶體中儲存方式如下:

|--------int--------|   4位元組

|--short-|----|----|    4位元組

而變數c是單獨存放在靜態資料區的,因此用siezof計算其大小時沒有將c所佔的空間計算進來。

而對於node5,裡面含有結構體變數

typedef struct

node5

s5;

sizeof(s5)=32。

對於變數a,其自身對齊引數為1,#pragma pack(n)為8,則a的最終對齊引數為1,為它分配1位元組的空間,它相對於結構體起始位址的偏移量為0,能被1整除;

對於s1,它的自身對齊引數為4(對於結構體變數,它的自身對齊引數為它裡面各個變數最終對齊引數的最大值),#pragma pack(n)為8,所以s1的最終對齊引數為4,接下來的位址相對於結構體起始位址的偏移量為1,不能被4整除,所以需要在a後面填充3位元組達到4,為其分配8位元組的空間;

對於變數b,它的自身對齊引數為8,#pragma pack(n)的預設值為8,則b的最終對齊引數為8,接下來的位址相對於結構體起始位址的偏移量為12,不能被8整除,所以需要在s1後面填充4位元組達到16,再為b分配8位元組的空間;

對於變數c,它的自身對齊引數為4,#pragma pack(n)的預設值為8,則c的最終對齊引數為4,接下來相對於結構體其實位址的偏移量為24,能夠被4整除,所以直接為c分配4位元組的空間。

此時結構體所佔位元組數為1+3+8+4+8+4=28位元組。

對於整個結構體來說,各個變數的最終對齊引數為1,4,8,4,最大值為8,#pragma pack(n)預設值為8,所以最終結構體的大小必須是8的倍數,因此需要在最後面填充4位元組達到32位元組。其儲存如下:

|--------bool--------|    4位元組

|---------s1---------|    8位元組

|--------------------|     4位元組

|--------double------|    8位元組

|----int----|---------|     8位元組

另外可以顯示地在程式中使用#pragma pack(n)來設定系統預設的對齊引數,在顯示設定之後,則以設定的值作為標準,其它的和上面所講的類似,就不再贅述了,讀者可以自行上機試驗一下。如果需要取消設定,可以用#pragma pack()來取消。

sizeof求結構體的大小時遇到的問題

運算子sizeof可以計算出給定型別的大小,對於32位系統來說,sizeof char 1 sizeof int 4。基本資料型別的大小很好計算,我們來看一下如何計算構造資料型別的大小。c語言中的構造資料型別有三種 陣列 結構體和共用體。陣列是相同型別的元素的集合,只要會計算單個元素的大小,整個陣列...

c語言 sizeof 求結構體大小

運算子sizeof可以計算出給定型別的大小,對於32位系統來說,sizeof char 1 sizeof int 4。基本資料型別的大小很好計算,我們來看一下如何計算構造資料型別的大小。c語言中的構造資料型別有三種 陣列 結構體和共用體。陣列是相同型別的元素的集合,只要會計算單個元素的大小,整個陣列...

sizeof求結構體大小的問題

標籤 空格分隔 c c 具體見 補充 另外乙個涉及資料型別以及記憶體儲存的問題 記憶體中的資料並非儲存在任意位址。處理器通常按照和其字大小相同的塊讀取記憶體資料 那麼考慮到效率因素,編譯器會按照塊大小的整數倍對記憶體中的實體進行位址對齊。因此在 32 位的處理器上,乙個 4 位元組整型資料肯定存放在...