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::$vbtable@:                     //虛基表
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::$vftable@:              //虛表
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::$vbtable@:  //虛基表
1> 0    | 0                   // 虛基指針距離派生類對象偏移0B
1> 1    | 8 (Bd(B+0)A)        // 虛基指針向下偏移8B找到虛基類
1>B::$vftable@:   //虛函數表
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::$vftable@B@:        //虛表
1>    | &B_meta
1>    |  0
1> 0    | &B::fb2     //派生類新增虛函數,放在最前面,訪問新增虛函數快一些,不用偏移 ,多一個虛函數指針,指向新的虛表
1>B::$vbtable@:                     //虛基表
1> 0    | -4                       //虛基指針距離派生類對象首地址的偏移信息
1> 1    | 8 (Bd(B+4)A)            //找到虛基類的偏移信息
1>B::$vftable@A@:                //虛表
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::$vftable@Base1@:
1>    | &Derived_meta
1>    |  0
1> 0    | &Derived::f(虛函數的覆蓋)    //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址
1> 1    | &Base1::g
1> 2    | &Base1::h
1> 3    | &Derived::g1        (新的虛函數,直接放在基類之後,加快查找速度)
1>Derived::$vftable@Base2@:
1>    | -8
1> 0    | &thunk: this-=8; goto Derived::f   //虛函數表還可以存放跳轉指令
1> 1    | &Base2::g
1> 2    | &Base2::h
1>Derived::$vftable@Base3@:
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@Base2@:
1>    | &Derived_meta
1>    |  0
1> 0    | &Derived::f    //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址
1> 1    | &Base2::g
1> 2    | &Base2::h
1> 3    | &Derived::g1
1>Derived::$vftable@Base3@:
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@Base1@:
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::$vftable@Derived@:
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::$vftable@Base1@:
1>    | -12
1> 0    | &Derived::f
1> 1    | &Base1::g
1> 2    | &Base1::h
1>Derived::$vftable@Base2@:
1>    | -20
1> 0    | &thunk: this-=8; goto Derived::f
1> 1    | &Base2::g
1> 2    | &Base2::h
1>Derived::$vftable@Base3@:
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::$vftable@B1@:
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::$vftable@B2@:
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::$vftable@B1@:
1>    | &D_meta
1>    |  0
1> 0    | &D::f1    // D中覆蓋了
1> 1    | &B1::Bf1 //新增
1> 2    | &D::Df   //D中新增,放到B1的虛函數表中
1>D::$vftable@B2@:
1>    | -16
1> 0    | &D::f2     // D中覆蓋了
1> 1    | &B2::Bf2  //新增
1>D::$vbtable@B1@:
1> 0    | -4        //距離派生類對象B1首地址偏移  -4
1> 1    | 36 (Dd(B1+4)B)
1>D::$vbtable@B2@:
1> 0    | -4        //距離派生類對象B2首地址偏移  -4
1> 1    | 20 (Dd(B2+4)B)
1>D::$vftable@B@:
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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...