当前位置: 首页 > news >正文

网络营销咨询网站源码多平台网页制作

网络营销咨询网站源码,多平台网页制作,简约wordpress模板,电子商务推荐类网站建设的目的这里写目录标题概述虚继承、虚函数涉及数据结构菱形继承虚继承接解决方案C 中虚继承 多态下vptr、vbptr分布机制MSVC(Microsoft ABI)对象布局GCC概述 在 C 中,虚继承用于解决菱形继承中重复基类的问题。通过 virtual 继承,编译器…

这里写目录标题

  • 概述
  • 虚继承、虚函数涉及数据结构
  • 菱形继承
  • 虚继承接解决方案
  • C++ 中虚继承 + 多态下vptr、vbptr分布机制
    • MSVC(Microsoft ABI)对象布局
    • GCC

概述

在 C++ 中,虚继承用于解决菱形继承中重复基类的问题。通过 virtual 继承,编译器保证无论通过多少条路径继承,虚基类只会保留一份副本。为了实现这一点,编译器会在中间类(如 Derived1、Derived2)中插入一个 vbptr(虚基类表指针),它指向一张 vbtable,表中记录虚基类在最终派生类对象中的偏移。

当通过中间类指针访问虚基类成员(如 ((Derived1*)&f)->a)时,编译器不能确定虚基类 Base 在对象内的具体位置,必须通过 vbptr → vbtable → 偏移 查表获得最终地址,再加到 this 指针上完成访问。这是虚继承访问的“查表 + 加偏移”机制。

相比之下,若通过 Final* 直接访问虚基类成员,编译器可以直接写死偏移量,无需查表。这也是为什么虚继承的访问路径取决于你是通过哪条继承路径访问的。

虚继承还要求虚基类的构造只能由最派生类完成,确保共享子对象只被构造一次。虚继承对象除了可能包含 vptr(虚函数表指针)用于虚函数多态外,还一定包含 vbptr + vbtable,用于虚基类定位,即使没有虚函数,也依然存在虚继承的结构开销。

虚继承、虚函数涉及数据结构

名称中文叫法用于什么机制存在哪?存什么内容?何时出现?
vtable虚函数表虚函数调用(多态)独立在代码段中虚函数地址数组类有虚函数时生成
vptr虚函数表指针指向 vtable每个含虚函数对象内指向对应的 vtable类含虚函数 → 对象含 vptr
vbtable虚基类偏移表虚继承偏移管理独立在代码段中虚基类的偏移信息类虚继承时生成
vbptr虚基类表指针指向 vbtable每个虚继承对象内指向对应的 vbtable类虚继承 → 对象含 vbptr

菱形继承

🎯 一、虚继承为什么出现?(问题背景)
当一个类 Final 同时继承自 Derived1Derived2,而这两个类又继承自同一个基类 Base,就会形成菱形继承结构:

     Base/    \
Derived1  Derived2\    /Final

问题

  • 如果是普通继承,Final 对象中会包含两份 Base 的副本。
  • 导致:
    • 数据冗余:有两份 Base::value,哪个才是有效的?
    • 二义性:访问 value 时不明确该用哪一个。
    • 资源释放多次:如果 Base 管理资源,可能析构两次导致崩溃。

虚继承接解决方案

🚧 二、虚继承解决方案与代价

class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
  • 虚拟继承保证 Base 子对象只存在一份,无论通过多少条路径继承过来。
  • “虚继承为了解决菱形继承重复基类问题,引入了 vbptr(虚基类表指针),通过它在运行时动态定位虚基类子对象。虚基类子对象通常放在派生类对象末尾,因此虚继承对象比普通继承对象更大,访问虚基类成员也多了一次间接寻址。”

🧠 三、内存布局详解(ASCII图)

class Base    { int a; };
class Derived1 : virtual public Base { int b; };
class Derived2 : virtual public Base { int c; };
class Final    : public Derived1, public Derived2 { int d; };
Final 对象 f 内存结构(简化布局):+----------------------+  ← Final对象起始地址 (f)
| Derived1::vbptr      |  ← Derived1 的虚基指针
+----------------------+
| b (Derived1 成员)     |
+----------------------+
| Derived2::vbptr      |  ← Derived2 的虚基指针
+----------------------+
| c (Derived2 成员)     |
+----------------------+
| d (Final 自身成员)    |
+----------------------+
| a (Base::a)          |  ← ✅ 唯一的 Base 子对象
+----------------------+
  • 中间派生类(如 Derived1并不拥有基类(如 Base)的成员变量,也不真正包含 Base 子对象,只有最派生类(如 Final)才真正拥有 Base 子对象的内存空间。
  • 中间派生类的内存布局里没有基类的成员函数吗
    • 虽然 Derived1 声明了继承 Base,但由于是虚继承,它并不会在自己的对象中嵌入 Base 子对象
    • 取而代之的是编译器插入一个 vbptr(虚基类表指针),用于在最终派生类里共享唯一一份 Base 子对象
  • ✅ 2. 那 Base 的成员函数去哪了?
    • 虽然 Derived1 不直接拥有 Base 的数据成员(如 int a)或函数成员(如 f()),但只要 Base::f() 是虚函数,那么:
      • Derived1虚函数表(vtable)仍然会保留 Base::f() 的指针
      • 真正的 Base 数据成员和虚表指针,只有在 最终派生类(如 Final 中才会被实际分配并共享。
      • 只要 Base 的函数是虚函数,它会在 Derived1 的虚表中出现,支持多态调用;但 Base 的实际数据和对象布局,只会在最终类(如 Final)中统一存在一份,由 vbptr 指向。

在虚继承中,由于 Base 只存在一份,它的位置由最派生类决定,不能提前写死偏移。所以中间类会插入一个 vbptr,指向虚基类表 vbtable,表中记录虚基类在最终对象中的偏移。访问时,先通过 vbptr 查出 Base 的偏移,加到 this 指针上,最终定位到 Base 成员。这就是虚继承访问路径的“查表 + 加偏移”机制。


🏗️ 五、构造函数执行顺序图(虚继承)

class Base    { Base()    { cout << "Base\n"; } };
class D1 : virtual public Base { D1() { cout << "D1\n"; } };
class D2 : virtual public Base { D2() { cout << "D2\n"; } };
class Final : public D1, public D2 { Final() { cout << "Final\n"; } };int main() { Final f; }Base
D1
D2
Final 

C++ 中虚继承 + 多态下vptr、vbptr分布机制

#include <iostream>
using namespace std;class A {
public:virtual void f();  // ✅ 虚函数 ⇒ vptrint a = 1;
};class B : virtual public A {
public:virtual void g();  // ✅ 虚函数 ⇒ vptrint b = 2;
};class C : virtual public A {
public:virtual void h();  // ✅ 虚函数 ⇒ vptrint c = 3;
};class D : public B, public C {
public:virtual void i();  // ✅ 虚函数 ⇒ vptrint d = 4;
};int main() {A a;B b;C c;D d;cout << "A: " << sizeof(A) << endl;cout << "B: " << sizeof(B) << endl;cout << "C: " << sizeof(C) << endl;cout << "D: " << sizeof(D) << endl;return 0;
}// ✅ 实现部分(必须加上)
void A::f() {}
void B::g() {}
void C::h() {}
void D::i() {}

实验发现,不同编译器下优化策略不一样

MSVC(Microsoft ABI)对象布局

A对象; 16字节

A a;
// 内存布局
8字节:a的虚表指针
4字节:成员变量
4字节:padding

image.png

B对象:40字节

B b;
// 内存布局
8字节:b的虚表指针
8字节:b的虚基表指针
4字节:b的成员变量
4字节:padding
8字节:a的虚表指针
4字节:a的成员变量
4字节:padding

image.png

C对象和B对象一样;

D:72字节 = b:24+c:24+d:8 + a:16

D d;
// 内存布局
8字节:b的虚表指针
8字节:b的虚基表指针
4字节:b的成员变量
4字节:padding
8字节:a的虚表指针
4字节:a的成员变量
4字节:padding

image.png

发现MVSC下c d成员竟然没优化到一起(这点不知道为啥???);

  • d直接访问abcd的成员,全是通过首地址+偏移访问;
  • D 自身通常不会额外持有一个独立的虚表指针(vptr)。其虚函数 D::i() 会被编译器优化地插入到继承路径中的某个已有虚表中(如 BC 的虚表)。因此,在内存布局中,你会看到 D 对象拥有 BCA 各自的 vptr 和 vbptr,但没有单独的 D::vptr,这体现了 C++ 编译器在虚函数表布局上的重用与优化策略。
  • ✅ D 如何访问 f()
    • D → B (vbptr_B) → offset_to_A → A 子对象 → vptr_A → f()
    • B 自己并不会再“拷贝” A::f() 进自己的虚表中。
  • D 直接访问 A::a
    • 编译器知道 A 在 D 中的偏移(比如 0x38),会直接用偏移访问;
  • D 通过 B 或 C 的方式访问 A
    • image.png
    • image.png

GCC

🌿 1. 类 A(普通虚函数类)

offset 0x00: vptr_A
offset 0x08: int a
offset 0x0C: padding
sizeof(A) = 16

B对象 (32字节) - 虚拟继承A:> 虽然 B 虚继承 A,在多继承中只保留一份 A,但 B 单独存在时还是附带一份自己的 A 虚基副本

B 对象(大小:32 bytes)
┌──────────── offset 0x00[0x00..0x07] vptr_B       ← B 的虚函数表指针
│ [0x08..0x0B] int b        ← 自己的成员
│ [0x0C..0x0F] padding      ← 对齐填充
├──────────── offset 0x10
│ A 虚基子对象(附带)      ← vptr_A + a
│   [0x10..0x17] vptr_A     ← 虚基类 A 的虚表指针
│   [0x18..0x1B] int a
│   [0x1C..0x1F] padding
└──────────── sizeof(B) = 32

C对象 (32字节) - 虚拟继承A:和 B 一样,虚继承 A;布局、大小、vptr 结构都完全一样,只需将 b 改为 cvptr_B 改为 vptr_C

C 对象(大小:32 bytes)
┌──────────── offset 0x00[0x00..0x07] vptr_C       ← B 的虚函数表指针
│ [0x08..0x0B] int c        ← 自己的成员
│ [0x0C..0x0F] padding      ← 对齐填充
├──────────── offset 0x10
│ A 虚基子对象(附带)      ← vptr_A + a
│   [0x10..0x17] vptr_A     ← 虚基类 A 的虚表指针
│   [0x18..0x1B] int a
│   [0x1C..0x1F] padding
└──────────── sizeof(C) = 32

🧩 D 对象内存布局(按字节)
假设在 64 位系统下,指针大小为 8 字节。
🎯 关键点(GCC Itanium):

  • 没有 vbptr
  • A 虚基类在 D 的尾部(只保留一份)
  • 虚基偏移存在于 vtable 的负索引槽中
  • 所有虚函数表指针放在子对象头部
  • D::d 会被尽可能塞入空隙区(如 C 的 padding)

在 GCC(Itanium ABI)下,B 即使虚继承了 A,它的对象中不包含显式的“虚基表指针(vbptr)”字段。

D 对象(大小:48 bytes)
┌──────────── offset 0x00
│ B 子对象(16 bytes)
│   [0x00..0x07] vptr_B-in-D   ← 指向 D 的 vtable(B 视图)
│   [0x08..0x0B] int b = 2[0x0C..0x0F] padding
├──────────── offset 0x10
│ C 子对象(16 bytes)
│   [0x10..0x17] vptr_C-in-D   ← 指向 D 的 vtable(C 视图)
│   [0x18..0x1B] int c = 3[0x1C..0x1F] int d = 4     ← D 的成员变量被紧贴在此处(复用 C 尾部空间)
├──────────── offset 0x20
│ A 虚基子对象(16 bytes)
│   [0x20..0x27] vptr_A-in-D   ← 指向 D 的 vtable(A 视图)
│   [0x28..0x2B] int a = 1[0x2C..0x2F] padding
└──────────── sizeof(D) = 48
http://www.dtcms.com/a/520247.html

相关文章:

  • 广州在建火车站在哪里国内最好的危机公关公司
  • mysql锁整理
  • linux下java程序使用jprofiler进行压测
  • R语言随机森林分析显示R方和P值
  • 《Python爬虫 + 飞书自动化上传》全流程详细讲解
  • ELK——logstash
  • 图像进行拼接-后进行ocr检测识别
  • 登封网站设计wordpress终极简码
  • 网站服务器关闭建设网站需要的安全设备
  • STM32 RS422异步UART通信测试全方案C++软件开发,嵌入式软件开发,Linux
  • Qt笔记:的初次使用Qt-Advanced-Docking-System
  • 基于PyTorch和CuPy的GPU并行化遗传算法实现
  • Apollo Canbus模块技术深度解析
  • DeepSeek-OCR 嵌入dify工作流
  • Linux小课堂: Vim与Emacs之Linux文本编辑器的双雄格局及Vim安装启动详解
  • 江宁外贸网站建设国内付费代理ip哪个好
  • 多种大连网站建设景观设计公司理念
  • KP201FLGA电机驱动电源方案SOT23-6恒压恒流恒功率电路原理图分析
  • Hadoop报错 Couldn‘t find datanode to read file from. Forbidden
  • 【案例实战】HarmonyOS分布式购物车:多设备无缝协同的电商体验
  • OpenCV工程中直接包含调用vs2022
  • 怎么看一个网站用什么做的北京建设公司有哪些
  • 上海交大刘鹏飞:智能不在于数据堆砌,78个样本训练出超强Agent,数据效率提升128倍
  • SpringAI1-快速⼊⻔
  • 本地局域网邮件管理系统:从原理到实现的完整指南
  • 面向小样本蜂窝网络故障诊断的模型与知识互增强方法
  • 上海网站推广方法河北石家庄属于几线城市
  • 专业购物网站建设哪家好免费找客户网站
  • 受欢迎的网站开发php源码搭建网站流程
  • 第八章 排序——课后习题解练【数据结构(c语言版 第2版)】