国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C++ > 正文

C++類實例內存結構分析(Boolan筆記第四周)

2019-11-08 18:40:43
字體:
來源:轉載
供稿:網友

我們來看一下C++類實例化的時候,它的各個成員在內存中的分布是怎么樣的。這個問題看似簡單,其實還是有許多情形需要考慮的:比如說類中是否有虛函數,子類與基類的實例內存結構有何區別,C/C++的內存對齊(比如4字節對齊或8字節對齊)對類的實例大小及內存分布有何影響? 一個空類的大小又是多少呢?

我們先看一個沒有虛函數的類Fruit及它的子類Apple:

class Fruit{ int _no; double _weight; char _key;public: Fruit(int no, double weight, char key): _no(no), _weight(weight), _key(key) {}};class Apple: public Fruit{ int _size; char _type;public: Apple(int no, double weight, char key, int size, char type): Fruit(no, weight, key), _size(size), _type(type) {}};

在Code::Blocks 8.02中運行如下代碼:

Fruit f1(1, 2.3, 'F'); Apple a1(2, 3.4, 'B', 5, 'A'); cout<<"sizeof(Fruit)="<<sizeof(f1)<<endl; cout<<"sizeof(Apple)="<<sizeof(a1)<<endl;

結果如下: sizeof(Fruit)=24 sizeof(Apple)=32

通過查看memory,可知f1和a1內存結構如下圖:

這里寫圖片描述

可以看出這里編譯器默認采用8字節對齊。在Apple對象中,Fruit的部分剛好位于其頂部。Apple的成員size跟Fruit的成員key及填充共用一個8字節。

我們可以看出一下幾點: 1. 子類的實例包含了基類的部分,并且基類的部分位于子類實例的開始部分; 2. 在沒有虛函數的時候,C++類的大小只與其數據成員有關, 它有沒有聲明函數或者在類中實現函數都不影響類的大小。這個其實很好理解,因為類里面函數的代碼對于該類的每個實例都是一樣的,所以它不是放在類的實例中,而是放在代碼段中,否則同一個類的每個實例都會額外占用大量內存。


下面我們再看一下有虛函數的情況。我們都知道C++的類有虛函數的時候, 類的object的會多一個vptr指針,指向vtbl。那么是不是一個類有了虛函數之后, 它的size就會增加4呢?

我們把上面兩個類都加上虛函數PRocess(),新的代碼如下:

class Fruit{ int _no; double _weight; char _key;public: Fruit(int no, double weight, char key): _no(no), _weight(weight), _key(key) {} virtual void process(){cout<<"Fruit::process()"<<endl;}};class Apple: public Fruit{ int _size; char _type;public: Apple(int no, double weight, char key, int size, char type): Fruit(no, weight, key), _size(size), _type(type) {} virtual void process(){cout<<"Apple::process()"<<endl;}};`

重新編譯。在Code::Blocks 8.02下運行結果為:

sizeof(Fruit)=24 sizeof(Apple)=32

那為什么加了vptr,類實例的大小不變呢? 根據查看memory,可知Fruit和Apple(有虛函數)的實例內存分配如下圖:

這里寫圖片描述

我們可以看出,Fruit和Apple類的實例的vptr位于最開始的位置,并且vptr和Fruit類的no合在一起組成一個8字節。

下面談談怎么敢斷定頭4個字節就是vptr呢? 事實上我們可以通過f1或a1的頭4個字節,看它指向什么地址,它指向的地址我們猜想應該是第一個虛函數的函數指針,我們通過這個函數指針來調用這個函數,看看是不是會打印出相應信息。

以f1為例: &f1 - 0x28ff10, f1的地址。 (int *)(&f1) - 0x28ff10, f1的地址轉換為int指針。 *(int *)(&f1) - 0x4452b0, f1的地址轉換為int,這就是vptr的值。 (int*)*(int*)(&f1) - 0x4452b0, vptr指向的內容轉換為int指針,它指向vtbl的第一項。 *(int*)*(int*)(&f1) - 0x41596c, vtbl第一項對應的值,它是一個指針,指向一個函數。我們下面會把它轉換成函數指針。

通過我們上面得到的指針,我們就可以調用這個函數了,看它是不是真的打印出了Fruit::process(),測試代碼如下:


typedef void(*FunPt)(void);FunPt pf;pf = (FunPt)*(int*)*(int*)(&f1);pf();

重新編譯,果然打印出了”Fruit::process()”。證明Fruit的頭4個字節就是它的vptr。

注意,我們也可以通過(*pf)()來調用這個函數。因為調用fun()和(*fun)()是等價的。這里為什么函數指針加不加*都可以調用呢?其實編譯器這里很清楚知道是要調用fun這個函數,所以兩種寫法都可以。但是如果是指針指向一個變量就不一樣了,編譯器不知道你是要訪問這個變量還是它的地址。

用同樣的方法(只需將上面的f1換成a1),我們也可以直接通過a1的頭4個字節得到其vptr,從而call a1的虛函數。結果打印出了”Apple::Process()”。這樣我們也驗證了a1的頭4個字節確實是它的vptr。


再考慮一下,如果Fruit有虛函數,Apple沒有定義新的虛函數,也沒有override Fruit的虛函數,那Apple的實例的內存分布如何呢?還會有vptr嗎?

class Fruit{ int _no; double _weight; char _key;public: Fruit(int no, double weight, char key): _no(no), _weight(weight), _key(key) {} virtual void process(){cout<<"Fruit::process()"<<endl;}};class Apple: public Fruit{ int _size; char _type;public: Apple(int no, double weight, char key, int size, char type): Fruit(no, weight, key), _size(size), _type(type) {}};

重新編譯,發現a1的size仍然為32。通過查看memory發現其內存分布與上圖是一樣的。通過頭4個字節vptr我們找到vtbl的第一項,將其轉換為函數指針后調用,我們發現其打印出了”Fruit::process()”。可見,如果基類有虛函數,子類沒有定義新的虛函數,也沒有對基類虛函數override的話,子類的實例仍然會有vptr,其指向一個vtbl,該vtbl的每一項都從基類的vtbl的相應項拷貝而來。


再思考一個問題,如果是一個空類,其實例的size是否為0呢?如果非0,其內容是什么?

class A{};A a;cout<<"sizeof(a)"<<siszeof(a)<<endl;

測試發現a的大小為1,通過查看memory發現該字節內容為0。可見C++編譯器對于空類為了能讓其實例化,仍然會給它分配一個字節的內存。注意單獨對于a而言,C/C++編譯器的sizeof()不會考慮內存對齊問題,但是如果a又是其他類的一部分,則就要考慮內存對齊了。

class B{ A a; int c;};B b;cout<<"sizeof(b)"<<siszeof(b)<<endl;

通過測試,sizeof(b)=8。可見空類仍然參與了字節對齊。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 林州市| 健康| 齐齐哈尔市| 当阳市| 汕头市| 灵山县| 老河口市| 宁化县| 涪陵区| 新竹市| 大田县| 海林市| 治多县| 平陆县| 合水县| 甘洛县| 和平县| 昆山市| 靖边县| 永州市| 顺义区| 中方县| 永修县| 陆川县| 彭州市| 晋城| 莱州市| 师宗县| 佛教| 灌南县| 图片| 易门县| 都昌县| 襄汾县| 乐都县| 肥东县| 白城市| 南阳市| 淮滨县| 武鸣县| 瑞丽市|