C++Day12 虛擬繼承記憶體佈局測試

来源:https://www.cnblogs.com/YongSir/archive/2023/01/26/17067682.html
-Advertisement-
Play Games

測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...


測試一、虛繼承與繼承的區別

1.1  單個繼承,不帶虛函數
1>class B    size(8):
1>    +---
1> 0    | +--- (base class A)
1> 0    | | _ia                       //4B
1>    | +---
1> 4    | _ib                        //4B

有兩個int類型數據成員,占8B,基類邏輯存在前面

1.2、單個虛繼承,不帶虛函數
1>class B    size(12):
1>    +---
1> 0    | {vbptr}        //虛基指針(指向虛基表)
1> 4    | _ib                          //派生類放到前面
1>    +---
1>    +--- (virtual base A)        //虛基類
1> 8    | _ia
1>    +---
1>B::[email protected]:                     //虛基表
1> 0    | 0                        // 虛基指針距離派生類對象偏移0B
1> 1    | 8 (Bd(B+0)A)            //  虛基指針向下偏移8B找到虛基類

虛繼承多一個虛基指針,共12B,虛擬繼承會將派生類的邏輯存到前面;

虛基表中存放的內容:(1)虛基指針距離派生類對象首地址的偏移信息(2)虛基類的偏移信息

 測試二、單個虛繼承,帶虛函數

2.1、單個繼承,帶虛函數
1>class B    size(12):
1>    +---
1> 0    | +--- (base class A)
1> 0    | | {vfptr}       //虛函數指針
1> 4    | | _ia
1>    | +---
1> 8    | _ib
1>    +---
1>B::[email protected]:              //虛表
1>    | &B_meta
1>    |  0
1> 0    | &B::f                      // f 和 fb2 入虛表,fb不是虛函數,不入虛表
1> 1    | &B::fb2                   // 派生類新增虛函數直接放在基類虛表中

帶虛函數的話,多一個虛函數指針,指向虛表,所以共占12B,派生類新增的虛函數放入基類虛表

2.3、單個虛繼承,帶虛函數,派生類不新增
8/16
1>class B    size(16):
1>    +---
1> 0    | {vbptr}    //有虛繼承的時候就多一個虛基指針,虛基指針指向虛基表  
1> 4    | _ib        //有虛函數的時候就產生一個虛函數指針,虛函數指針指向虛函數表
1>    +--- 
1>    +--- (virtual base A)
1> 8    | {vfptr}
1>12    | _ia
1>    +---
1>B::[email protected]:  //虛基表
1> 0    | 0                   // 虛基指針距離派生類對象偏移0B
1> 1    | 8 (Bd(B+0)A)        // 虛基指針向下偏移8B找到虛基類
1>B::[email protected]:   //虛函數表
1>    | -8         
1> 0    | &B::f

兩個 int 型變數,一個虛函數指針,一個虛基指針,共占16B;

虛擬繼承使得派生類邏輯存在基類前面;

(虛擬繼承後,基類在派生類後面,虛函數指針也在下麵,派生類要找到虛函數表,向後偏移8B)

2.2 單個虛繼承,帶虛函數 (自己新增1>class B    size(20):
1>    +---
1> 0    | {vfptr}    //虛函數指針
1> 4    | {vbptr}    //虛基指針  (虛繼承多一個)  {虛擬繼承,派生類在前面}
1> 8    | _ib
1>    +---
1>    +--- (virtual base A)    
1>12    | {vfptr}           //虛函數指針
1>16    | _ia
1>    +---
1>B::[email protected]@:        //虛表
1>    | &B_meta
1>    |  0
1> 0    | &B::fb2     //派生類新增虛函數,放在最前面,訪問新增虛函數快一些,不用偏移 ,多一個虛函數指針,指向新的虛表
1>B::[email protected]:                     //虛基表
1> 0    | -4                       //虛基指針距離派生類對象首地址的偏移信息
1> 1    | 8 (Bd(B+4)A)            //找到虛基類的偏移信息
1>B::[email protected]@:                //虛表
1>    | -12
1> 0    | &B::f    基類佈局在最後面

派生類中新增一個虛函數指針,指向一張新的虛表,存放派生類新增的虛函數,可以更快的訪問到

所以,兩個虛函數指針,一個虛基指針,兩個int類型變數,共20B

 

 

 

 測試三:多重繼承(帶虛函數)

3.1普通多重繼承,帶虛函數,自己有新增虛函數
28   //Base1中 f() g() h() , Base2中 f() g() h() , Base3中 f() g() h() Derived 中 f() g1() 
1>class Derived    size(28):
1>    +---
1> 0    | +--- (base class Base1)            //基類有自己的虛函數表,基類的佈局按照被繼承時的順序排列
1> 0    | | {vfptr}                         // 3個虛函數指針指向不同虛表
1> 4    | | _iBase1
1>    | +---
1> 8    | +--- (base class Base2)
1> 8    | | {vfptr}
1>12    | | _iBase2
1>    | +---
1>16    | +--- (base class Base3)
1>16    | | {vfptr}
1>20    | | _iBase3
1>    | +---
1>24    | _iDerived
1>    +---
1>Derived::[email protected]@:
1>    | &Derived_meta
1>    |  0
1> 0    | &Derived::f(虛函數的覆蓋)    //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址
1> 1    | &Base1::g
1> 2    | &Base1::h
1> 3    | &Derived::g1        (新的虛函數,直接放在基類之後,加快查找速度)
1>Derived::[email protected]@:
1>    | -8
1> 0    | &thunk: this-=8; goto Derived::f   //虛函數表還可以存放跳轉指令
1> 1    | &Base2::g
1> 2    | &Base2::h
1>Derived::[email protected]@:
1>    | -16
1> 0    | &thunk: this-=16; goto Derived::f
1> 1    | &Base3::g
1> 2    | &Base3::h

Base1、Base2、Base3中各有一個虛函數指針指向自己的虛表,有4個int類型的數據成員,共占28B

第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址

3.2、虛擬多重繼承,帶虛函數,自己有新增虛函數(只有第一個是虛繼承)
32  Base1是虛繼承
1>class Derived    size(32):    //多一個虛基指針
1>    +---
1> 0    | +--- (base class Base2)
1> 0    | | {vfptr}
1> 4    | | _iBase2
1>    | +---
1> 8    | +--- (base class Base3)
1> 8    | | {vfptr}
1>12    | | _iBase3
1>    | +---
1>16    | {vbptr}
1>20    | _iDerived
1>    +---
1>    +--- (virtual base Base1)
1>24    | {vfptr}
1>28    | _iBase1
1>    +---
1>Derived::$vftable@[email protected]:
1>    | &Derived_meta
1>    |  0
1> 0    | &Derived::f    //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址
1> 1    | &Base2::g
1> 2    | &Base2::h
1> 3    | &Derived::g1
1>Derived::$vftable@[email protected]:
1>    | -8                                //去找Derived::f
1> 0    | &thunk: this-=8; goto Derived::f
1> 1    | &Base3::g
1> 2    | &Base3::h
1>Derived::$vbtable@:   //虛基表
1> 0    | -16
1> 1    | 8 (Derivedd(Derived+16)Base1)
1>Derived::$vftable@[email protected]:
1>    | -24
1> 0    | &thunk: this-=24; goto Derived::f
1> 1    | &Base1::g
1> 2    | &Base1::h

虛擬繼承會將派生類的邏輯存到前面,Base1是虛繼承,所以記憶體中的存放順序為 Base2、Base3、Derived、Base1

所占空間大小,在上面一個例子基礎上,多一個虛基指針,所以占32B

虛基指針向上偏移16B得到派生類對象首地址,向下偏移8B找到虛基類

3.3、虛擬多重繼承,帶虛函數,自己有新增虛函數(三個都是虛繼承)
36
1>class Derived    size(36):    //多一張虛表
1>    +---
1> 0    | {vfptr}                         //以空間換時間   新增虛函數,多張虛表
1> 4    | {vbptr}
1> 8    | _iDerived
1>    +---
1>    +--- (virtual base Base1)
1>12    | {vfptr}
1>16    | _iBase1
1>    +---
1>    +--- (virtual base Base2)
1>20    | {vfptr}
1>24    | _iBase2
1>    +---
1>    +--- (virtual base Base3)
1>28    | {vfptr}
1>32    | _iBase3
1>    +---
1>Derived::[email protected]@:
1>    | &Derived_meta
1>    |  0
1> 0    | &Derived::g1
1>Derived::$vbtable@:
1> 0    | -4
1> 1    | 8 (Derivedd(Derived+4)Base1)  //vbptr偏移8B找到虛基類Base1
1> 2    | 16 (Derivedd(Derived+4)Base2)  // vbptr偏移16B找到虛基類Base2
1> 3    | 24 (Derivedd(Derived+4)Base3)
1>Derived::[email protected]@:
1>    | -12
1> 0    | &Derived::f
1> 1    | &Base1::g
1> 2    | &Base1::h
1>Derived::[email protected]@:
1>    | -20
1> 0    | &thunk: this-=8; goto Derived::f
1> 1    | &Base2::g
1> 2    | &Base2::h
1>Derived::[email protected]@:
1>    | -28
1> 0    | &thunk: this-=16; goto Derived::f
1> 1    | &Base3::g
1> 2    | &Base3::h

虛擬繼承會將派生類的邏輯存到前面,3個Base都是虛繼承,所以記憶體中的存放順序為Derived、Base1、 Base2、Base3

在上一個例子的基礎上,多一張虛表,所以占36B

 

 

 測試四、菱形虛繼承

4.1、菱形普通繼承(存儲二義性)
48  B1、B2繼承B;D繼承B1、B2
class D    size(48):
1>    +---
1> 0    | +--- (base class B1)
1> 0    | | +--- (base class B)
1> 0    | | | {vfptr}
1> 4    | | | _ib             //存儲二義性
1> 8    | | | _cb  //1
1>      | | | <alignment member> (size=3) //記憶體對齊
1>    | | +---
1>12    | | _ib1
1>16    | | _cb1
1>      | | <alignment member> (size=3)
1>    | +---
1>20    | +--- (base class B2)
1>20    | | +--- (base class B)
1>20    | | | {vfptr}
1>24    | | | _ib           //存儲二義性
1>28    | | | _cb
1>      | | | <alignment member> (size=3)
1>    | | +---
1>32    | | _ib2
1>36    | | _cb2
1>      | | <alignment member> (size=3)
1>    | +---
1>40    | _id
1>44    | _cd
1>      | <alignment member> (size=3)
1>    +---
1>D::[email protected]@:
1>    | &D_meta
1>    |  0
1> 0    | &D::f
1> 1    | &B::Bf
1> 2    | &D::f1
1> 3    | &B1::Bf1
1> 4    | &D::Df
1>D::[email protected]@:
1>    | -20
1> 0    | &thunk: this-=20; goto D::f
1> 1    | &B::Bf
1> 2    | &D::f2
1> 3    | &B2::Bf2

B的數據成員有兩份,造成了存儲二義性,共占48B

4.2、菱形虛擬繼承        B1、B2虛擬繼承B;D普通繼承B1、B2
52
1>class D    size(52):
1>    +---
1> 0    | +--- (base class B1)        //基類B1
1> 0    | | {vfptr}
1> 4    | | {vbptr}        // +36 找到虛基類
1> 8    | | _ib1
1>12    | | _cb1
1>      | | <alignment member> (size=3)
1>    | +---               
1>16    | +--- (base class B2)      //基類B2
1>16    | | {vfptr}
1>20    | | {vbptr}      // +20找到虛基類
1>24    | | _ib2
1>28    | | _cb2
1>      | | <alignment member> (size=3)
1>    | +---
1>32    | _id          //派生類D
1>36    | _cd
1>      | <alignment member> (size=3)
1>    +---
1>    +--- (virtual base B)   //基類B
1>40    | {vfptr}
1>44    | _ib
1>48    | _cb
1>      | <alignment member> (size=3)
1>    +---
1>D::$[email protected]@:
1>    | &D_meta
1>    |  0
1> 0    | &D::f1    // D中覆蓋了
1> 1    | &B1::Bf1 //新增
1> 2    | &D::Df   //D中新增,放到B1的虛函數表中
1>D::[email protected]@:
1>    | -16
1> 0    | &D::f2     // D中覆蓋了
1> 1    | &B2::Bf2  //新增
1>D::$vbtable@[email protected]:
1> 0    | -4        //距離派生類對象B1首地址偏移  -4
1> 1    | 36 (Dd(B1+4)B)
1>D::$vbtable@[email protected]:
1> 0    | -4        //距離派生類對象B2首地址偏移  -4
1> 1    | 20 (Dd(B2+4)B)
1>D::[email protected]@:
1>    | -40
1> 0    | &D::f
1> 1    | &B::Bf

B1、B2各有虛基指針

存儲順序本來是:派生類B1、基類B、派生類B2、基類B、派生類D

存儲順序:派生類B1、派生類B2、派生類D、基類B(基類放到後面,解決了存儲二義性)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 Proteus 是世界上唯一將電路模擬軟體、PCB設計軟體和虛擬模型模擬軟體三合一的設計平臺。 Proteus 8.15 現已發佈,本篇將帶領大家安裝此版本。 介紹 Proteus Proteus 軟體是英國 Lab Center Electronics 公司出版的 EDA 工具軟體。它不僅具 ...
  • 一道貪心演算法不是很明顯的題目,其實一般的遞推也可以做。 大體思路:肯定優先購買單價最低的奶農的牛奶,那麼就需要先根據牛奶單價進行排序,這裡用結構體會更好一點。之後在從前往後一個一個枚舉,直至購買的牛奶數量達到要求即可。 話不多說,上代碼: 1 #include<bits/stdc++.h> 2 us ...
  • 2023-01-24 一、NoSQL資料庫 1、NoSQL資料庫的簡介 NoSQL(NoSQL=Not Only SQL),即“不僅僅是SQL”,泛指非關係型的資料庫。NosQL不依賴業務邏輯方式存儲,而以簡單的key-value模式存儲。因此大大的增加了資料庫的擴展能力。 (1)不遵循SQL標準 ...
  • 前言 最近群里遇到獲取Route名為空的問題,當時沒在意。。。 直到自己在監控頁面啟動耗時,需要確定當前頁面是哪個從而方便標記它載入的耗時時,遇到同樣 route.settings.name 為空問題,模擬場景如下: 在 main.dart 頁面中點擊 + 按鈕跳轉到 TestPage2 頁面。 M ...
  • Lspatch的使用。xp模塊可以使用戶獲得應用原本所沒有的功能。使用模塊需要修改應用。Lspatch實現了無需Root修改應用。 ...
  • 前端面試題學習-HTML-個人總結 這是看別人總結的基礎上再度總結的,總結的鏈接如下 鏈接 1. DOCTYPE 的作用? 告知瀏覽器解析器用何標準解析文檔,若不指定則按相容模式進行解析(向後相容模擬老瀏覽器)。 IE5.5 引入的概念。 HTML5 之後無需指定,因為在之前的都是基於 SGML 的 ...
  • JavaScript 中有兩種類型轉換:隱式類型轉換和顯式類型轉換。 隱式類型轉換指 JavaScript 在運行時自動將一種類型轉換為另一種類型。例如,在數學運算中,JavaScript 會將字元串轉換為數字。 顯式類型轉換指在代碼中使用內置函數或全局對象將一種類型顯式地轉換為另一種類型。例如,使 ...
  • 商品系統是電商系統最基礎、最核心的系統之一。商品數據遍佈所有業務,首頁、門店頁、購物車、訂單、結算、售後、庫存、價格等,都離不開商品。商品信息要穩定提供至到家供應鏈的每個節點,所以必須要有一套穩定的、高性能的商品服務體系支撐。 隨著京東到家商品業務的快速發展,業務從單一轉變為多元化,系統功能設... ...
一周排行
    -Advertisement-
    Play Games
  • @ 先看一下導出的整體效果(如下圖),其中標註的區域都是通過後臺動態生成的: 一、先在Word中建立好表格模板 1.1、參數創建方法(Word和WPS) 1.1.1、Office中Word域的創建 1.1.1.1、選中指定的單元格 -> 點擊頭部工具欄中的”插入“ -> 選擇 ”文檔部件“ -> 選 ...
  • 在實際工作中,經常會有一些需要定時操作的業務,如:定時發郵件,定時統計信息等內容,那麼如何實現才能使得我們的項目整齊劃一呢?本文通過一些簡單的小例子,簡述在.Net6+Quartz實現定時任務的一些基本操作,及相關知識介紹,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 紙殼CMS支持將評論、留言、表單提交、訂閱等通知,通過WebHook發送到第三方平臺,比如釘釘。 創建釘釘WebHook 需要在釘釘群中創建自定義機器人,具體方法可以參考釘釘的官方文檔: 自定義機器人接入 需要註意的是,在安全設置中不要使用加簽,使用自定義關鍵字即可。在發送的消息中,只要包含這個關鍵 ...
  • 向下轉型的使用 Java的多態性: 父類指向子類的聲明 Animal animal = new Dog()//Dog()重寫了父類Animal 有了對象的多態性以後,記憶體實際上載入的是==子類==的屬性和方法,但是由於變數聲明為==父類類型==,導致編譯時只能調用父類的屬性和方法,子類特有的屬性方法 ...
  • spring源碼環境搭建 組件 版本 jdk 1.8.0_192 spring-framework 5.3.x gradle 7.5.1 idea 2022.3.3 aspectJ 1.9 可根據spring-framwork項目說明靈活選擇 一、拉取spring-framework項目 1、spr ...
  • 首先任何的商業邏輯,光流量增長,沒法變現是沒用的。 就像博客群發提效工具,得有對應的用戶,更得有對應付費用戶群體的畫像。剩下的就是靠增長,被動讓他們找到你的產品,用產品解決他們痛點,他們自然而然會付費。 下麵大致分享下從三個方向分享下: 用戶痛點 -> 真正的付費用戶群體 產品價值 PLG 增長 一 ...
  • Object類的使用 Object類 Object類中的方法可以在網上搜索得到 Object類是所有java類的父類 如果類在聲明中未使用extends關鍵字指明其父類,則預設父類為java.lang.Object類 Object類中的功能(屬性、方法)具有通用性。 屬性:無 方法:equals() ...
  • Qt 源碼分析之moveToThread 這一次,我們來看Qt中關於將一個QObject對象移動至一個線程的函數moveToThread Qt使用線程的基本方法 首先,我們簡單的介紹一下在Qt中使用多線程的幾種方法: 重寫QThread的run函數,將要在多線程執行的任務放到run函數里 /*myt ...
  • 包裝類的使用 包裝類的使用 java提供8種基本數據類型對應的包裝類,使得基本數據類型變數具有類的特征 掌握:==基本數據類型、包裝類、String==三者之間的互相轉換 自動裝箱與自動拆箱==[基本數據類型和包裝類的轉換]== JDK5.0新特性,自動裝箱與自動拆箱。 class Test{ pu ...
  • 本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 大家好,我是大彬~ 今天來聊聊接 ...