跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++java

C++ 虚函数表与多态实现原理

C++ 的运行时多态依赖对象中的 vptr 和对应的 vtable。带虚函数的类通常会比普通类多出一个虚表指针,虚函数本身仍在代码段中,虚表只是保存这些函数入口地址。通过基类指针或引用调用虚函数时,编译器会在运行时根据对象实际类型查表,从而完成动态绑定;不满足这个条件的调用则在编译期直接确定目标地址,属于静态绑定。文章还通过示例说明了派生类重写后虚表项如何被替换,以及虚表地址验证的思路。

随缘发布于 2026/6/300 浏览
C++ 虚函数表与多态实现原理

前言

多态真正落到机器层面,靠的不是语法糖,而是 vptr 虚指针和 vtable 虚函数表。很多人第一次学这块都会卡在几个点上:为什么带虚函数的类 sizeof 会变大、基类指针怎么在运行时找到派生类实现、虚表和虚指针到底放在哪、静态绑定和动态绑定差在哪。把这些问题串起来看,C++ 多态就没那么玄乎了。

虚函数和普通函数的区别

普通类里,成员函数不计入对象大小,对象的内存占用主要由成员变量和对齐决定。可一旦类里出现虚函数,对象里通常会多出一个虚函数表指针 vptr,在 32 位环境下常见是 4 字节,64 位环境下常见是 8 字节。这个指针指向当前类型对应的虚函数表。

虚函数本身并不'住'在对象里。它和普通函数一样,编译后都是代码段里的函数入口,只是这些入口地址会被收进虚表。

多态是怎么工作的

C++ 的运行时多态,关键在'通过基类指针或引用调用虚函数'这个前提上。编译器不会在编译期把调用目标彻底定死,而是先从对象里取出 vptr,再顺着虚表找到对应函数地址,最后完成调用。对象实际是谁,调用的就是谁的实现。

class Person {
public:
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; }
private:
    string _name;
};
class Student : public Person {
public:
    virtual void BuyTicket() { cout << "买票 - 打折" << endl; }
private:
    string _id;
};
class Soldier : public Person {
public:
    virtual void BuyTicket() { cout << "买票 - 优先" << endl; }
private:
    string _codename;
};
void Func(Person* ptr) {
    // 这里调用的是 ptr 指向对象实际类型对应的 BuyTicket
    ptr->BuyTicket();
}
int main() {
    Person ps;
    Student st;
    Soldier sr;
    Func(&ps);
    Func(&st);
    Func(&sr);
    return 0;
}

这段代码里,Func 只认识 Person*,但真正执行哪个版本的 BuyTicket,取决于指针指向的对象类型。这个行为就是多态。

静态绑定和动态绑定

不是所有函数调用都会等到运行时再决定。

  • 不满足多态条件的调用,通常在编译期就确定目标地址,这叫静态绑定。
  • 满足多态条件的调用,会在运行时根据对象实际类型查表,这叫动态绑定。

从汇编角度看,动态绑定就是多了一层'先取虚表,再取函数地址'的过程。静态绑定则直接跳到已知地址,编译器不会绕这一步。

动态绑定示例:

; ptr 是指针 + BuyTicket 是虚函数,满足多态条件
mov eax, dword ptr [ptr]
mov edx, dword ptr [eax]
mov esi, esp
mov ecx, dword ptr [ptr]
mov eax, dword ptr [edx]
call eax

静态绑定示例:

; BuyTicket 不是虚函数,不满足多态条件
; 编译器直接确定调用地址
mov ecx, dword ptr [ptr]
call Student::Student

虚函数表里放了什么

虚函数表本质上就是一个保存函数指针的数组。基类和派生类各自有自己的虚表,同一个类型的对象通常共享同一张虚表。

派生类对象里,继承来的那部分通常仍然带着虚表指针,但这个指针已经指向派生类自己的虚表了。虚函数一旦被重写,虚表中对应位置就会被替换成派生类版本的地址;派生类新增的虚函数,也会按顺序放进虚表。

常见情况下,虚表末尾还会看到一个 0x00000000 标记,不过这不是 C++ 标准要求的行为,更像是某些编译器的实现习惯,不能把它当成标准结论。

虚表到底放在哪,标准其实没有规定。不同编译器、不同平台实现都可能不一样。在 VS 的常见实现里,它通常落在只读数据相关区域;你前面看到'常量区'的说法,更多是在这个语境下成立。

代码验证

class Base {
public:
    virtual void func1() { cout << "Base::func1" << endl; }
    virtual void func2() { cout << "Base::func2" << endl; }
    void func5() { cout << "Base::func5" << endl; }
protected:
    int a = 1;
};
class Derive : public Base {
public:
    virtual void func1() { cout << "Derive::func1" << endl; }
    virtual void func3() { cout << "Derive::func1" << endl; }
    void func4() { cout << "Derive::func4" << endl; }
protected:
    int b = 2;
};
int main() {
    Base b;
    Derive d;
    Base* p3 = &b;
    Derive* p4 = &d;
    printf("Base 虚表地址:%p\n", *(int*)p3);
    printf("Derive 虚表地址:%p\n", *(int*)p4);
    printf("虚函数地址:%p\n", &Base::func1);
    printf("普通函数地址:%p\n", &Base::func5);
    return 0;
}

这段代码的目的很直接:把对象首地址强转后取出虚表指针,再和普通成员函数地址做对比。运行结果通常能看出,虚表地址和对象首部绑定得很紧,而普通函数地址只是代码段里的一个入口。

小结

多态不是'对象会变身',而是对象里多了一个能指路的表头,调用时根据实际类型查到正确的函数入口。理解了 vptr、vtable 和动态绑定,很多看起来模糊的现象——比如对象大小变化、虚函数调用路径、派生类覆盖行为——就都能对上号了。

目录

  1. 前言
  2. 虚函数和普通函数的区别
  3. 多态是怎么工作的
  4. 静态绑定和动态绑定
  5. 虚函数表里放了什么
  6. 代码验证
  7. 小结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 华为机试:素数伴侣的二分图匹配解法
  • 大模型面试题整理:RAG、SFT、RLHF 与核心架构
  • USB-Blaster 驱动安装与 FPGA 下载排障
  • Open3D.Art 生成模型到拓竹打印的实用流程
  • Python 3.11 新特性:性能、异常与类型系统的变化
  • RISC-V 处理器从 RTL 到 FPGA 验证实录
  • CoPaw 部署与定制实操笔记
  • IntelliJ IDEA 2026.1 EAP:Java 26、Spring Boot 4 与 Gradle 9 适配
  • 用 WebGIS 和百度天气做一个复古天气预报页
  • YOLOv8 无人机道路病害识别的工程落地思路
  • 在安卓上用 Termux 跑 Debian 和桌面应用
  • 双指针滑动窗口:4 道经典题的思路拆解
  • NWPU VHR-10 遥感目标检测与 YOLO 实践
  • 老龄化压力下护理机器人的发展与分化
  • Python 缓存策略:从 dict 到 LRU 与分布式实战
  • 宏智树 AI 学术写作平台评测
  • 文心一言 4.5:中文能力实测与本地部署记录
  • 在 WSL2 上部署 OpenClaw 的实操记录
  • Oh My Open Code:把单模型 IDE 变成多模型协作系统
  • 大语言模型词表裁剪的实现思路与代码

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online