淺談C++結(jié)構(gòu)體和類,對象的大小該如何計算?

轉(zhuǎn)載自http://www.youranshare.com/blog/sid/92.html

C++中霞幅,結(jié)構(gòu)體和類它們都是有構(gòu)造函數(shù)娩脾、析構(gòu)函數(shù)和成員函數(shù)的赵誓,他們兩者的根本區(qū)別就是:結(jié)構(gòu)體中訪問控制默認(rèn)是public的,而類中默認(rèn)的訪問控制是private的。對于C++中的結(jié)構(gòu)體而言俩功,public幻枉、protectedprivate的訪問都是在編譯期進(jìn)行檢查的绑雄,當(dāng)越權(quán)訪問的時候展辞,編譯過程中會給出此類的錯誤并給與提示,在編譯成功后万牺,程序在執(zhí)行的過程中不會有任何的檢查和限制罗珍,這一點你可以通過類的指針偏移做一下測試。因此在反匯編中脚粟,C++中的結(jié)構(gòu)體與類沒有分別覆旱,兩者的原理是相同的,只是類型名稱不同核无。

說一下對象內(nèi)存的布局

對于類和對象的關(guān)系想必你已經(jīng)很了解了扣唱,類是一個抽象的概念,對象是一個實例化的具體存在的团南。這里我們以一個簡單的類噪沙,談一下類與對象的關(guān)系:

#include <iostream>
using namespace std;
  
class CNumber{
public:
      CNumber(){
           m_One = 1;
           m_Two = 2;
      }
      int GetNumberOne(){
           return m_One;
      }
      int GetNumberTwo(){
           return m_Two;
      }
private:
      int m_One;
      int m_Two;
};

int main()
{
      CNumber num;
      cout << num.GetNumberOne() << endl;
  
      //嘗試著用指針訪問
      cout << *(int*)(void*) &num << endl;
      cout << *((int*)(void*)&num + 1) << endl;
      return 0;
}

我們知道類的私有成員是不能被直接訪問到的,但是我們在程序運行后通過指針的偏移還是依然可以讀取內(nèi)存的數(shù)據(jù),就像結(jié)構(gòu)體一樣吐根,如圖所示我們的運行結(jié)果:

運行結(jié)果

可以發(fā)現(xiàn)正歼,將對象的指針強制轉(zhuǎn)換成我們需要的數(shù)據(jù)類型,然后通過指針在內(nèi)存中的偏移可以訪問到對象的私有成員!這也就說明了C++中類保護(hù)屬性是編譯的時候進(jìn)行檢測的!

C++中類的成員變量和結(jié)構(gòu)體一樣拷橘,也是按照順序依次放到內(nèi)存中的局义,先定義的數(shù)據(jù)成員放到地地址,后定義的數(shù)據(jù)成員放到高地址處冗疮,但是對象的大小只包含數(shù)據(jù)成員萄唇,類的成員函數(shù)屬于執(zhí)行代碼,不屬于類對象的數(shù)據(jù)术幔。

你可以通過sizeof獲取到CNumber對象的大小另萤,它們的大小都是8Byte,這8Byte字節(jié)是由類中的兩個數(shù)據(jù)成員主城特愿,它們都是int類型仲墨,格子的長度都為4Byte。從內(nèi)存的布局上來看揍障,類與一個數(shù)組非常相似目养,都是由多個數(shù)據(jù)成員組成,但是類的能力要遠(yuǎn)遠(yuǎn)大過數(shù)組毒嫡。類的成員變量類型定義非常廣泛癌蚁,除了本身對象之外幻梯,任何已知的數(shù)據(jù)類型都可以在內(nèi)種作為成員變量進(jìn)行定義。

為什么在類中不能定義自身的對象呢努释?因為類需要在申請內(nèi)存的過程中計算出自身的實際大小碘梢,用于實例化對象。如果你在類中定義了自身的對象變量伐蒂,那么在計算類中各個數(shù)據(jù)成員長度時煞躬,又會因為回到自身導(dǎo)致無限遞歸下去,而這個遞歸沒有出口逸邦,所以不能在類中定義自身的對象成員變量恩沛。但是指針是可以的,因為指針的長度是確定的缕减,也就是相當(dāng)于一個常量值雷客,因此定義自身的指針是不會影響到類本身大小的計算的。根據(jù)上面所說的東西桥狡,我們是否可以定義一個公式用于描述類的對象長度呢搅裙?也許你會認(rèn)為下面的公式可以用于計算對象的長度:

? 對象長度= sizeof(成員1) + …+szieof(成員2)+…+sizeof(成員n)

這里我明確的告訴你,這個公式是錯誤的裹芝,對象大小的計算遠(yuǎn)遠(yuǎn)沒有那么簡單部逮,即是我們拋開虛函數(shù)和繼承的原因,仍然有三種情況能夠推翻此公式:

  1. 空類

  2. 內(nèi)存對齊問題

  3. 靜態(tài)數(shù)據(jù)成員

當(dāng)出現(xiàn)上面的情況時嫂易,類的對象長度的計算就需要小心了:

空類: 就是說類內(nèi)部沒有任何數(shù)據(jù)成員甥啄,但是實際上類對象的長度可是為1字節(jié)

內(nèi)存對齊: 在VC++6.0中,類和結(jié)構(gòu)體中的數(shù)據(jù)成員是根據(jù)它們在類或者結(jié)構(gòu)體中出現(xiàn)的順序來依次申請內(nèi)存的炬搭,但是由于內(nèi)存對齊的原因,它們并不會一定像數(shù)組那樣內(nèi)存是連續(xù)排列的穆桂,由于數(shù)據(jù)類型不同宫盔,因此占用的內(nèi)存空間大小也會不同,在申請內(nèi)存的時候會按照一定的規(guī)則.

我們以一個結(jié)構(gòu)體為例子來看看這個結(jié)構(gòu)體中成員變量地址的排列:

struct TagTest{
          char a;
          int b;
     };
結(jié)構(gòu)大小

如圖所示享完,可以看到a ,b的地址相差為4字節(jié)灼芭,這也就是說a,b這兩個變量是沒有連續(xù)的分配在內(nèi)存中的,這中現(xiàn)象就是內(nèi)存地址對齊導(dǎo)致的.

在為結(jié)構(gòu)體和類中的數(shù)據(jù)成員分配內(nèi)存的時候般又,結(jié)構(gòu)體中的當(dāng)前數(shù)據(jù)成員類型長度為M彼绷,指定對齊值為N(編譯器指定的,例如為8Byte)茴迁,那么實際上的對齊值q = Min(M,N)寄悯,也就是說這個數(shù)據(jù)成員所在的地址必須為q的倍數(shù),所以在上面的結(jié)構(gòu)體中堕义,數(shù)據(jù)成員b猜旬,它的對齊值為q=Min(4,8) 也就是4,所以b的地址必須為4的倍數(shù),雖然前面的數(shù)據(jù)成員的地址a18086720洒擦,b只是占用了1個字節(jié)椿争,但是b的地址需要為4的倍數(shù),那么就需要在數(shù)據(jù)成員a后面偏移3個字節(jié)的位置放置b熟嫩,如圖所示的ab的內(nèi)存分布:

地址分布

下面我們來分析一下a,b的地址分配過程:

首先, 開始分配a的地址秦踪,a為一個char類型,它的對齊值M=1掸茅,在VC++6.0中編譯器的默認(rèn)對齊值為8椅邓,那么q = Min(1,8),也就是q=1,地址需要是1的倍數(shù)倦蚪,所以直接分配地址就行了希坚。例如分配了地址為18086720(十進(jìn)制)

其次,開始分配b的地址,b是一個int類型對齊值為4Byte陵且,那么q = Min(4,8)裁僧,也就是q = 4,它所在的地址需要是4的倍數(shù)慕购,如果此時我們直接將b分配在a的后面聊疲,那么b的地址將會是18086721,不滿足對齊的地址沪悲,所以需要往后偏移3個字節(jié)获洲,對應(yīng)的地址就是18086724,這個地址滿足對齊值4殿如,所以系統(tǒng)給b分配的地址為18086724贡珊,分配完畢。

通過上面的分配我們可以看到涉馁,這個結(jié)構(gòu)體一共占用了8Byte门岔,而不是我們認(rèn)為的5字節(jié)。另外值得說的是a后面的那3個字節(jié)里面可不是填充的0x00烤送,一般系統(tǒng)都會填充0xCC寒随。

看完上面的東西,有些童鞋肯定感覺到了一個問題:“結(jié)構(gòu)體本身也是一種數(shù)據(jù)類型帮坚,既然是數(shù)據(jù)類型妻往,就像int一樣,結(jié)構(gòu)體本身也是有對齊的吧“.

答案是肯定的试和,數(shù)據(jù)類型都是有對齊值的讯泣,數(shù)據(jù)結(jié)構(gòu)本身也是一種類型,與int基本類型無差別灰署,當(dāng)然也存在對齊值的問題,下面我給出一個數(shù)據(jù)結(jié)構(gòu):

struct STest{
    double da; // 8字節(jié)大小
    int    ib; // 4字節(jié)大小
    short  sc; // 2字節(jié)大小
};

這個數(shù)據(jù)結(jié)構(gòu)STest的大小是16而不是14判帮,為什么局嘁?

這是因為結(jié)構(gòu)體本身也是一種數(shù)據(jù)類型,當(dāng)然它也有對應(yīng)的字節(jié)對齊處理晦墙,這里我們將會討論一下對齊值對結(jié)構(gòu)體整體大小的影響悦昵,如果按照VC++6.0默認(rèn)的8字節(jié)對齊,那么對于一個結(jié)構(gòu)體來說它的對齊值依然滿足公式q=Min(M,N)VC++6.0N=8)晌畅,但是需要注意的是這里的M應(yīng)該是結(jié)構(gòu)體中的數(shù)據(jù)成員類型的最大值但指,就像結(jié)構(gòu)體STest,它的對齊值按照最大的數(shù)據(jù)成員double da抗楔,對齊值也就是 8Byte棋凳,這樣一來可以計算出結(jié)構(gòu)體STest的對齊值為8,所以編譯器在STest的最后一個成員short sc后面有增加了2個字節(jié)用于填充結(jié)構(gòu)體,使得整體大小為16字節(jié)连躏,這樣就滿足了對齊的要求剩岳。

通過上面的介紹可以看出,結(jié)構(gòu)體的對齊值是根據(jù)結(jié)構(gòu)體內(nèi)部最大的成員長度動態(tài)調(diào)整的入热,可不是固定的8字節(jié)拍棕,也可以是4字節(jié)。

C++中勺良,雖然存在默認(rèn)的對齊值绰播,但是這個默認(rèn)值也是可以修改的,我們可以使用預(yù)編譯指令 #pragma pack(N)來指定對齊大小尚困,例如我們下面的代碼蠢箩,將對齊值設(shè)置為1字節(jié):

#pragma pack(1)
struct STest{
    double da; // 8字節(jié)大小
    int    ib; // 4字節(jié)大小
    short  sc; // 2字節(jié)大小
};

運行結(jié)果如下圖所示,這里的sizeof計算出的結(jié)構(gòu)就是 14字節(jié)了:

img

經(jīng)過預(yù)編譯指令后事甜,將對齊值調(diào)整為1字節(jié)谬泌,根據(jù)對齊規(guī)則,q=Min(4,1)得出對齊值為1字節(jié)逻谦,既然是1字節(jié)呵萨,辣么就不用想了,直接就是14字節(jié)的大小了跨跨。

但是要注意的是,你使用#pragma pack(N)設(shè)置的對齊值可不一定會生效的囱皿,這是因為q=Min(M,N)勇婴,要知道你的N要是太大的話~~q最終還是等于M的,就像你設(shè)置對齊值為128嘱腥,根本沒啥用嘛= =耕渴,對齊值的計算流程總的來說:將設(shè)定的對齊值與結(jié)構(gòu)體中最大的基本類型數(shù)據(jù)成員的長度進(jìn)行比較,取兩者之間的較小者齿兔。

當(dāng)結(jié)構(gòu)體中以數(shù)組作為成員的時候橱脸,將會根據(jù)數(shù)組 元素類型 的長度計算對齊值础米,而不是按照數(shù)組的整體大小去計算。

結(jié)構(gòu)體含有數(shù)組類型的對齊

當(dāng)結(jié)構(gòu)體中以數(shù)組作為成員的時候添诉,將會根據(jù) 數(shù)組元素 的長度計算對齊值屁桑,而不是根據(jù)數(shù)組的整體長度來計算,例如下面的代碼:

Struct{
      Char cChar;         // 占用一個字節(jié)內(nèi)存
      Char cArray[4];     // 占用多少字節(jié)內(nèi)存呢栏赴?
      Short sShort;       // 應(yīng)該占用2字節(jié)內(nèi)存
}

按照對齊的規(guī)定蘑斧,cCharcArry它們都是char類型的數(shù)據(jù),內(nèi)存對齊沒有縫隙须眷,不需要插入空白的數(shù)據(jù)竖瘾。但是當(dāng)cArraysShort對齊的時候,cCharcArray已經(jīng)在內(nèi)存中占用了5個字節(jié)花颗,此時按照結(jié)構(gòu)體中當(dāng)前的數(shù)據(jù)類型short進(jìn)行對齊的時候捕传,就需要在cArray后面在插入一個字節(jié)就OK了,其結(jié)構(gòu)如下圖所示:

結(jié)構(gòu)

結(jié)構(gòu)體內(nèi)部的數(shù)據(jù)成員已經(jīng)對齊了扩劝,下面就是處理結(jié)構(gòu)體本身的對齊值問題了庸论。根據(jù)結(jié)構(gòu)體中的數(shù)據(jù)成元類型得到,最大的數(shù)據(jù)成員sShort2個字節(jié)今野,其余成員都是1字節(jié)的大小葡公,在默認(rèn)的情況下,對齊值為8条霜,根據(jù)公式q = Min(M,N) 計算得出該結(jié)構(gòu)體的對齊值為2催什,而此時結(jié)構(gòu)體的總大小為8字節(jié),也就是說無需填入數(shù)據(jù)即可滿足對齊要求.

當(dāng)結(jié)構(gòu)體中出現(xiàn)結(jié)構(gòu)體類型的數(shù)據(jù)成員的時候宰睡,不會將嵌套的結(jié)構(gòu)體類型的整體長度參與到對齊值的計算中蒲凶,而是以嵌套定義的結(jié)構(gòu)體所使用的對齊值進(jìn)行對齊計算,如下面的代碼所示:

struct tagOne
{
      char cChar;     // 占用1字節(jié)
      char cArray[4]; // 占用5字節(jié)
      short sShort;   // 占用2字節(jié)
};
struct tagTwo
{
      int nInt;     // 占用4字節(jié)
      tagOne one;   // 占用8字節(jié)
};

在上面的結(jié)構(gòu)體中拆内,雖然tagOne占用了8字節(jié)大小旋圆,但是由于其對齊值為2,所以在計算tagTwo的對齊值的時候參數(shù)one的對齊值是2麸恍,所以根據(jù)對齊計算的公式q=Min(M,N)得出數(shù)據(jù)結(jié)構(gòu)tagTwo的對齊值為4灵巧,占用了12個字節(jié)!而不是8對齊占用16字節(jié)抹沪。

靜態(tài)數(shù)據(jù)成員

當(dāng)類中的數(shù)據(jù)成員被修飾為靜態(tài)成員的時候刻肄,對象的計算方法又會發(fā)生變化。因為雖然靜態(tài)數(shù)據(jù)成員是在類內(nèi)部進(jìn)行定義融欧,但是它與靜態(tài)局部變量是類似的敏弃,存放的位置和全局變量一致。只是編譯器增加了作用域的檢查噪馏,在作用域外不可見麦到,同類對象將共享有靜態(tài)數(shù)據(jù)成員的空間.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绿饵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓶颠,更是在濱河造成了極大的恐慌拟赊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件步清,死亡現(xiàn)場離奇詭異要门,居然都是意外死亡,警方通過查閱死者的電腦和手機廓啊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門欢搜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谴轮,你說我怎么就攤上這事炒瘟。” “怎么了第步?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵疮装,是天一觀的道長。 經(jīng)常有香客問我粘都,道長廓推,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任翩隧,我火速辦了婚禮樊展,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堆生。我一直安慰自己专缠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布淑仆。 她就那樣靜靜地躺著涝婉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔗怠。 梳的紋絲不亂的頭發(fā)上墩弯,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音寞射,去河邊找鬼最住。 笑死,一個胖子當(dāng)著我的面吹牛怠惶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轧粟,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼策治,長吁一口氣:“原來是場噩夢啊……” “哼脓魏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起通惫,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茂翔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后履腋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊燎,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年遵湖,在試婚紗的時候發(fā)現(xiàn)自己被綠了悔政。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡延旧,死狀恐怖谋国,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迁沫,我是刑警寧澤芦瘾,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站集畅,受9級特大地震影響近弟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挺智,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一祷愉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逃贝,春花似錦谣辞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沪摄,卻和暖如春躯嫉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杨拐。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工祈餐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哄陶。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓帆阳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屋吨。 傳聞我的和親對象是個殘疾皇子蜒谤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,967評論 6 13
  • 轉(zhuǎn)載 結(jié)構(gòu)體對齊詳解 結(jié)構(gòu)體數(shù)據(jù)成員對齊的意義 許多實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制山宾,它們會...
    erU閱讀 475評論 0 3
  • 1. C++基礎(chǔ)知識點 1.1 有符號類型和無符號類型 當(dāng)我們賦給無符號類型一個超出它表示范圍的值時,結(jié)果是初始值...
    Mr希靈閱讀 17,987評論 3 82
  • 這樣的偶遇鳍徽,實在有趣资锰。 從那日的詩詞古韻, 到今天的思維碰撞阶祭, 從那天的老康頭绷杜, 到今日的王老師。 幾天來的眼福濒募,...
    芥末女字閱讀 372評論 0 0
  • 他是我見過最好看的人
    Leeyutttt閱讀 244評論 0 0