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
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...