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

【C++进阶】一文吃透静态绑定、动态绑定与多态底层机制(含虚函数、vptr、thunk、RTTI)

【C++进阶】一文吃透静态绑定、动态绑定与多态底层机制(含虚函数、vptr、thunk、RTTI)

作者:你的C++教练
日期:2025-08-01


目录

  1. 静态绑定 vs 动态绑定
  2. 非虚函数的三大坑
  3. 多态的四要素
  4. 虚析构函数为什么必须写?
  5. 探秘 vptr/vftablethunk
  6. RTTI 与 dynamic_cast 的底层真相
  7. 虚继承下的虚表偏移
  8. 性能、inline 与构造语义
  9. 实战代码与汇编级分析


1️⃣ 静态绑定 vs 动态绑定

绑定类型决定时机典型场景性能
静态绑定 (Static Binding)编译期普通成员函数、缺省实参直接 call,零额外开销
动态绑定 (Dynamic Binding)运行期虚函数通过指针/引用调用一次 vptr 解引用 + 间接 call

一句话:“指针/引用 + 虚函数”才会触发动态绑定,否则全是静态绑定。


2️⃣ 非虚函数的三大坑

① 普通函数静态绑定
struct B { void foo() { puts("B"); } };
struct D : B { void foo() { puts("D"); } };B* p = new D;
p->foo();          // 输出 B!(静态绑定)
② 缺省实参静态绑定
struct B {virtual void f(int x = 1) { cout << x; }
};
struct D : B {void f(int x = 2) override { cout << x; }
};B* p = new D;
p->f();  // 输出 1!缺省值来自 B 的定义
③ 非虚析构函数 → 内存泄漏
B* p = new D;
delete p;   // 只调 ~B(),~D() 不会被调用

3️⃣ 多态的四要素

条件说明
继承存在父子类
虚函数父类至少一个 virtual
重写子类覆盖父类虚函数
指针/引用用父类指针/引用指向子类对象

示例:

class A { public: virtual void vf() { puts("A"); } };
class B : public A { void vf() override { puts("B"); } };A* p = new B;
p->vf();   // 动态绑定,输出 B

4️⃣ 为什么必须写虚析构函数?

Base* p = new Derived;
delete p;  // 只有 ~Base() 是 virtual,才会:
  1. 先通过 vptr 找到 Derived::~Derived
  2. 执行 ~Derived
  3. 自动插入 ~Base()
  4. 最终 operator delete 释放内存

结论:任何可能被继承的类,析构函数都写成 virtual


5️⃣ 探秘 vptr / vftable / thunk

对象模型(简化)
对象地址
├─ vptr ----┐
├─ 成员变量 │
│           │
v           v+--------------+
vftable | &Base::foo   |  <-- 如果未被覆盖+--------------+| &Derived::bar|+--------------+
thunk 是什么?
  • 当用 第二基类指针 指向多重继承的子对象时,需要调整 this 偏移。
  • 编译器生成一段 汇编桩代码(thunk)放在虚表中:
    thunk:sub  this, offset   ; 调整 thisjmp  Derived::foo   ; 真正虚函数
    
  • 虚表项指向的就是 thunk 地址。

6️⃣ RTTI 与 dynamic_cast

if (Derived* d = dynamic_cast<Derived*>(basePtr)) {d->onlyInDerived();
}
实现原理
  • 每个有虚函数的类都会在虚表 -1 位置type_info 指针。
  • dynamic_cast 通过 vptr[-1] 比较 RTTI 信息,决定转换是否成功。

7️⃣ 虚继承下的虚表偏移

struct VBase { virtual void vf(); };
struct A : virtual VBase { void vf() override; };
  • 虚基类子对象在内存中可能位于 对象尾部
  • vptr 需要 间接寻址 才能找到虚基类中的虚函数,带来额外一次指针解引用。

8️⃣ 性能 & inline 提醒

因素开销
虚函数调用一次额外内存读取
多重继承可能增加 thunk
虚继承两次指针解引用
inline 失败递归、过大、地址取址都会阻止

建议:性能关键路径避免深度虚继承,热点函数尽量 final/inline


9️⃣ 构造语义 & 汇编级分析

伪代码回顾
C::C() {B::B();               // 基类构造A::A();           // 再基类vptr = A::vftable;vptr = B::vftable;vptr = C::vftable;    // 最终态
}
  • 构造期间 对象类型不断变化,虚表指针逐级覆盖。
  • 析构期间 反向逐级回退,保证 dynamic_cast/typeid 行为正确。

🔚 结论速记

规则口诀
需要多态“指针引用 + virtual”
析构函数“能继承就 virtual”
缺省实参“静态绑定,别在虚函数里玩默认值”
RTTI“至少一个 virtual 才能 dynamic_cast
性能“虚函数=一次间接寻址,虚继承=两次”
http://www.lryc.cn/news/607668.html

相关文章:

  • 测试分类:详解各类测试方式与方法
  • 使用gcc代替v语言的tcc编译器提高编译后二进制文件执行速度
  • Trust Management System (TMS)
  • MySQL锁的分类 MVCC和S/X锁的互补关系
  • Linux编程: 10、线程池与初识网络编程
  • GESP2025年6月认证C++八级( 第三部分编程题(1)树上旅行)
  • 链表【各种题型+对应LeetCode习题练习】
  • 《C++》STL--list容器详解
  • UnionApplication
  • 江协科技STM32 12-2 BKP备份寄存器RTC实时时钟
  • 【Shell脚本自动化编写——报警邮件,检查磁盘,web服务检测】
  • Windows安装虚拟机遇到内容解码失败
  • python-异常(笔记)
  • Java学习-运算符
  • Java:JWT 从原理到高频面试题解析
  • 【Linux】重生之从零开始学习运维之Mysql
  • Rust在CentOS 6上的移植
  • 2025.8.1
  • 1661. 每台机器的进程平均运行时间
  • 系统开机时自动执行指令
  • 基于python大数据的招聘数据可视化及推荐系统
  • 算法思想之 多源 BFS 问题
  • 【Node.js安装注意事项】-安装路径不能有空格
  • PNP机器人机器人学术年会展示灵巧手动作捕捉方案。
  • MySQL分析步
  • Android签名轮转
  • Conda install安装了一些库,如何撤销操作
  • 第13届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2022年3月13日真题
  • 外卖“0元购”退场后,即时零售大战才刚开始
  • CORS模块:你的跨域快速通行证 [特殊字符]