天天看點

sizeof(struct)和sizeof(union)的結果分析及其原因

一個錯誤

有的時候,在腦海中停頓了很久的“顯而易見”的東西,其實根本上就是錯誤的。就拿下面的問題來看:

struct T
{
    char ch;
    int i;
};      

使用sizeof(T),将得到什麼樣的答案呢?要是以前,想都不用想,在32位機中,int是4個位元組,char是1個位元組,是以T一共是5個位元組。實踐出真知,在VS中測試了下,答案是8個位元組。再來一個例子:

union foo
{
    char s[10];
    int i;
};      

我們知道共用體表示幾個變量共用一個記憶體位置,是以,想不用想,sizeof(foo)=10,然後,馬上被打臉!啪啪啪的,哎,反正受傷的總是我,我已經有點麻木了,還是老老實實的接受吧!實踐出真知,在VS中測試了下,答案是12個位元組。為什麼答案和自己想象的有出入呢?這裡将引入記憶體對齊這個概念。

記憶體對齊概念及其提出的原因

許多計算機系統對其内置類型的存放位置有一定的限制,通常會按照一定的倍數k存放相應的類型,這種方式即為記憶體對齊。其中k稱為記憶體模數。

若sizeof(struct S1)/sizeof(struct S2)>1 則稱S1的對齊要比S2的對齊嚴格,反之相反。

為什麼要提出記憶體對齊?

比如這麼一種處理器,它每次讀寫記憶體的時候都從某個8倍數的位址開始,一次讀出或寫入8個位元組的資料,假如軟體能保證double類型的資料都從8倍數位址開始,那麼讀或寫一個double類型資料就隻需要一次記憶體操作。否則,我們就可能需要兩次記憶體操作才能完成這個動作,因為資料或許恰好橫跨在兩個符合對齊要求的8位元組記憶體塊上。(在有些處理器上記憶體不對齊的話可能會出現錯誤)

一、struct結構體(C++)在沒有設定#pragma pack宏的情況下

下面用前面的例子來說明VC到底怎麼樣來存放結構的。

struct MyStruct
{
  double dda1;
  char dda;
  int type;
};      

為上面的結構配置設定空間的時候,VC根據成員變量出現的順序和對齊方式,

先為第一個成員dda1配置設定空間,其起始位址跟結構的起始位址相同(偏移量0為sizeof(double)的倍數),該成員變量占用sizeof(double)=8個位元組;

接下來為第二個成員dda配置設定空間,這時下一個可以配置設定的位址對于結構的起始位址的偏移量為8,是sizeof(char)的倍數,是以把dda存放在偏移量為8的地方滿足對齊方式,該成員變量占用sizeof(char)=1個位元組;

接下來為第三個成員type配置設定空間,這時下一個可以配置設定的位址對于結構的起始位址的偏移量為9,不是sizeof(int)=4的倍數,為了滿足對齊方式對偏移量的限制問題,編譯器自動填充3個位元組(這三個位元組沒有放什麼東西),這時下一個可以配置設定的位址對于結構的起始位址的偏移量為12,剛好是sizeof(int)=4的倍數,是以把type存放在偏移量為12的地方,該成員變量占用sizeof(int)=4個位元組;

這時整個結構的成員變量已經都配置設定了空間,總的占用的空間大小為:8+1+3+4=16,剛好為結構的位元組邊界數(即結構中占用最大空間的類型所占用的位元組數sizeof(double)=8)的倍數,是以沒有空缺的位元組需要填充。

是以整個結構的大小為:sizeof(MyStruct)=8+1+3+4=16,其中有3個位元組是編譯器自動填充的,沒有放任何有意義的東西。

再舉個例子,交換一下上面的MyStruct的成員變量的位置,使它變成下面的情況:

struct MyStruct
{
  char dda;
  double dda1;
  int type;
};      

這個結構占用的空間為多大呢?sizeof(MyStruc)為24。

原因分析:

先為第一個成員dda配置設定空間,偏移量為0為sizeof(char)的倍數,滿足對齊方式,dda占用1個位元組;

接下來為第二個成員dda1配置設定空間,這時下一個可以配置設定的位址對于結構的起始位址的偏移量為1,不是sizeof(double)的倍數,需要補足7個位元組才能使偏移量變為8(滿足對齊方式),是以編譯器自動填充7個位元組,dda1存放在偏移量為8的位址上, 它占用8個位元組。

接下來為第三個成員type配置設定空間,這時下一個可以配置設定的位址對于結構的起始位址的偏移量為16,是sizeof(int)=4的倍數,滿足int的對齊方式,是以不需要自動填充,type存放在偏移量為16的位址上,它占用4個位元組。

所有成員變量都配置設定了空間,空間總的大小為1+7+8+4=20,不是結構的節邊界數(即結構中占用最大空間的類型所占用的位元組數sizeof(double)=8)的倍數,是以需要填充4個位元組,以滿足結構的大小為sizeof(double)=8的倍數。

是以該結構總的大小為:sizeof(MyStruc)為1+7+8+4+4=24。其中總的有7+4=11個位元組是編譯器自動填充的,沒有放任何有意義的東西。

#pragma pack(n)

編譯器中提供了#pragma pack(n)來設定變量以n位元組對齊方式。//n為1、2、4、8、16...

n位元組對齊就是說變量存放的起始位址的偏移量有兩種情況:

  • 第一,如果n大于等于該變量所占用的位元組數,那麼偏移量必須滿足預設的對齊方式,即該變量所占用位元組數的整數倍;
  • 第二,如果n小于該變量的類型所占用的位元組數,那麼偏移量為n的倍數,不用滿足預設的對齊方式。

同時,結構的總大小也有個限制條件,分下面兩種情況:

  • 第一,如果n大于所有成員變量類型所占用的位元組數,那麼結構的總大小必須為占用空間最大的變量占用的空間數的倍數;
  • 第二,如果n小于等于所有成員變量類型所占用的位元組數,那麼必須為n的倍數。

比如,在上面的代碼前加一句#pragma pack(1),就是沒有對齊規則。比如上面的兩個例子,sizeof(MyStruc)都是13

#pragma pack(1)
struct MyStruct
{
  double dda1;
  char dda;
  int type;
};
cout << sizeof(MyStruct) << endl;      

二、union共用體

共用體表示幾個變量共用一個記憶體位置,在不同的時間儲存不同的資料類型和不同長度的變量。在union中,所有的共用體成員共用一個空間,并且同一時間隻能儲存其中一個成員變量的值。

當一個共用體被聲明時, 編譯程式自動地産生一個變量, 其長度為union中占用最大空間的類型的整數倍,且要大于等于其最大成員所占的存儲空間。(重要重要重要!!!)

在給共用體的元素指派時,實際上會發生互相覆寫的情況,當我們讀取一個被覆寫掉的值時會得到什麼結果,就取決于計算機的大小端位元組序的模式了。關于這個的讨論,這裡就不展開了。

是以,剛開始的問題:

union foo
{
    char s[10];
    int i;
};      

sizeof(foo) = 12,原因是,首先要滿足是int類型的長度4的倍數,同時要大于等于char數組的長度10,是以,得到12。

當在共用體中包含結構體時,如下:

typedef struct inner
{
  char c1;
  double d;
  char c2;
}Inner;

union data4
{
  Inner t1;
  int i;
  char c;
};
cout << sizeof(data4) << endl;      

sizeof(data4) = 24。剛開始,這個答案我是拒絕的!不應該是16嗎?struct,第一個偏移位置放char,1不是sizeof(double)=8的倍數,是以,補充7個位元組,從偏移位置8,放double,從偏移位置16,放char。最後,16又是結構體中最大類型double的倍數。完美!但是,我們求得是所占空間,而不是偏移位置!!!所占空間:1 + 7 + 8 + 1 = 17。17不是結構體中最大類型double的倍數,需要補充7個位元組,得到24。

而union所占空間,一是最大類型所占空間的倍數,二是要大于等于最大成員的長度。得到24。

當在結構體中包含共用體時,

typedef union
{
  long i;
  int k[5];
  char c;
}DATE;

struct data
{
  int cat;
  char cc;
  DATE cow;
  char a[6];
};

cout << sizeof(data) << endl;      

sizeof(data) = 36,共用體在結構體裡的對齊位址為共用體本身内部所對齊位數,sizeof(DATE)=20, 而在結構體中中是4+1+3(補齊4對齊)+20+6+2(補齊4對齊)=36;

三、sizeof用法總結

sizeof有着許多的用法,而且很容易引起一些錯誤。下面根據sizeof後面的參數對sizeof的用法做個總結。

A.參數為資料類型或者為一般變量。例如sizeof(int),sizeof(long)等等。這種情況要注意的是不同系統系統或者不同編譯器得到的結果可能是不同的。例如int類型在16位系統中占2個位元組,在32位系統中占4個位元組。

B.參數為數組或指針。下面舉例說明

int a[50]; //sizeof(a)=4*50=200;sizeof(a)求數組所占的空間大小

int *a=new int[50];// sizeof(a)=4; a為一個指針,sizeof(a)是求指針的大小,在32位系統中,當然是占4個位元組。      

C.參數為結構或類。sizeof應用在類和結構的處理情況是相同的。

但有兩點需要注意:

第一、結構或者類中的靜态成員不對結構或者類的大小産生影響,因為靜态變量的存儲位置與結構或者類的執行個體位址無關。

第二、沒有成員變量的結構或類的大小為1,因為必須保證結構或類的每一個執行個體在記憶體中都有唯一的位址。

Class Test{int a;static double c};//sizeof(Test)=4.

Test *s;//sizeof(s)=4,s為一個指針。

Class test1{ };//sizeof(test1)=1;

class Test2 
{
public:
  virtual void Print(){}
};//sizeof(Test2) = 4;      

繼續閱讀