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
  • 最近做項目過程中,使用到了海康相機,官方只提供了C/C++的SDK,沒有搜尋到一個合適的封裝了的C#庫,故自己動手,簡單的封裝了一下,方便大家也方便自己使用和二次開發 ...
  • 前言 MediatR 是 .NET 下的一個實現消息傳遞的庫,輕量級、簡潔高效,用於實現進程內的消息傳遞機制。它基於中介者設計模式,支持請求/響應、命令、查詢、通知和事件等多種消息傳遞模式。通過泛型支持,MediatR 可以智能地調度不同類型的消息,非常適合用於領域事件處理。 在本文中,將通過一個簡 ...
  • 前言 今天給大家推薦一個超實用的開源項目《.NET 7 + Vue 許可權管理系統 小白快速上手》,DncZeus的願景就是做一個.NET 領域小白也能上手的簡易、通用的後臺許可權管理模板系統基礎框架。 不管你是技術小白還是技術大佬或者是不懂前端Vue 的新手,這個項目可以快速上手讓我們從0到1,搭建自 ...
  • 第1章:WPF概述 本章目標 瞭解Windows圖形演化 瞭解WPF高級API 瞭解解析度無關性概念 瞭解WPF體繫結構 瞭解WPF 4.5 WPF概述 ​ 歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢 ...
  • 在日常開發中,並不是所有的功能都是用戶可見的,還在一些背後默默支持的程式,這些程式通常以服務的形式出現,統稱為輔助角色服務。今天以一個簡單的小例子,簡述基於.NET開發輔助角色服務的相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 第3章:佈局 本章目標 理解佈局的原則 理解佈局的過程 理解佈局的容器 掌握各類佈局容器的運用 理解 WPF 中的佈局 WPF 佈局原則 ​ WPF 視窗只能包含單個元素。為在WPF 視窗中放置多個元素並創建更貼近實用的用戶男面,需要在視窗上放置一個容器,然後在這個容器中添加其他元素。造成這一限制的 ...
  • 前言 在平時項目開發中,定時任務調度是一項重要的功能,廣泛應用於後臺作業、計劃任務和自動化腳本等模塊。 FreeScheduler 是一款輕量級且功能強大的定時任務調度庫,它支持臨時的延時任務和重覆迴圈任務(可持久化),能夠按秒、每天/每周/每月固定時間或自定義間隔執行(CRON 表達式)。 此外 ...
  • 目錄Blazor 組件基礎路由導航參數組件參數路由參數生命周期事件狀態更改組件事件 Blazor 組件 基礎 新建一個項目命名為 MyComponents ,項目模板的交互類型選 Auto ,其它保持預設選項: 客戶端組件 (Auto/WebAssembly): 最終解決方案裡面會有兩個項目:伺服器 ...
  • 先看一下效果吧: isChecked = false 的時候的效果 isChecked = true 的時候的效果 然後我們來實現一下這個效果吧 第一步:創建一個空的wpf項目; 第二步:在項目裡面添加一個checkbox <Grid> <CheckBox HorizontalAlignment=" ...
  • 在編寫上位機軟體時,需要經常處理命令拼接與其他設備進行通信,通常對不同的命令封裝成不同的方法,擴展稍許麻煩。 本次擬以特性方式實現,以兼顧維護性與擴展性。 思想: 一種命令對應一個類,其類中的各個屬性對應各個命令段,通過特性的方式,實現其在這包數據命令中的位置、大端或小端及其轉換為對應的目標類型; ...