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
  • 前言 插件化的需求主要源於對軟體架構靈活性的追求,特別是在開發大型、複雜或需要不斷更新的軟體系統時,插件化可以提高軟體系統的可擴展性、可定製性、隔離性、安全性、可維護性、模塊化、易於升級和更新以及支持第三方開發等方面的能力,從而滿足不斷變化的業務需求和技術挑戰。 一、插件化探索 在WPF中我們想要開 ...
  • 歡迎ReaLTaiizor是一個用戶友好的、以設計為中心的.NET WinForms項目控制項庫,包含廣泛的組件。您可以使用不同的主題選項對項目進行個性化設置,並自定義用戶控制項,以使您的應用程式更加專業。 項目地址:https://github.com/Taiizor/ReaLTaiizor 步驟1: ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • Channel 是乾什麼的 The System.Threading.Channels namespace provides a set of synchronization data structures for passing data between producers and consume ...
  • efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...