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

C++虚函数表深度解析|看懂多态底层汇编

前言

很多同学学多态只背结论:虚函数实现多态,父类指针指向子类对象调用子类虚函数,但是不知道内存里面发生了什么。本文从内存布局、虚表、汇编指令、大小计算,把虚函数表底层讲清楚。

一、带虚函数的类对象内存布局

class Base { public: virtual void Func1() { cout << "Func1()" << endl; } protected: int _b = 1; char _ch = 'x'; };

32 位环境下求sizeof(Base)

内存构成

  1. _vfptr虚函数表指针(虚表指针),32 位占 4 字节,放在对象内存最开头。

  2. 普通成员变量:int _b(4 字节),char _ch(1 字节),内存对齐补齐 3 字节。

32 位下总大小:4 + 4 + 1 + 3(对齐) = 12,答案选 D。

重点:只要类里面有 virtual 虚函数,每个对象开头都会多一个虚表指针,不是类共用一份指针,是每个对象都存指针;而虚函数表本身,同类所有对象共享一张

什么是虚函数表 _vftable

虚函数表本质:函数指针数组,数组里面存放本类所有虚函数的地址。

  • _vfptr:对象内指针,指向这张函数指针数组。

  • 同类型多个对象,_vfptr都指向同一个虚表,节省内存。

  • 不同类(父类、派生类)拥有各自独立虚表。

二、派生类虚表,重写 (override) 发生了什么

  1. 派生类先继承父类虚表指针;

  2. 如果派生类重写父类虚函数:派生类虚表对应位置,覆盖成自己重写后的函数地址

  3. 没有重写的虚函数,直接拷贝父类虚函数地址;

  4. 派生类自己新增虚函数,追加到虚表数组后面。

父类对象 和 子类对象 的虚表指针,指向两张完全不同的虚函数表。

图示:

  • Person 虚表:[Person::BuyTicket, Person::Func2...]

  • Student 重写 BuyTicket 之后,Student 虚表:[Student::BuyTicket, Person::Func2...]

对应位置直接替换,其他虚函数保留父类版本。

三、多态运行:编译 vs 运行(看汇编最直观)

Person* ptr = new Student; ptr->BuyTicket();

✅满足多态条件(虚重写 + 指针 / 引用):运行时寻址

汇编指令流程:

  1. ptr拿到对象地址

  2. 从对象起始位置取出虚表指针_vfptr

  3. 根据偏移,去虚函数表数组里面拿对应虚函数地址

  4. call 这个函数地址

真正调用哪个函数,运行的时候才确定。指针本身类型不重要,看指针指向的真实对象的虚表。

❌不满足多态:普通调用,编译期直接定死地址

Person p; p.BuyTicket();

编译阶段直接把Person::BuyTicket的硬编码地址写死到 call 指令,直接调用,不走虚表。

核心区别总结

  • 多态调用:运行时,去对象虚表里找函数地址

  • 普通成员调用:编译期直接确定函数地址

四、虚函数表存放在内存哪个区段

C++ 标准没有规定虚表放在哪里,由编译器实现决定。

  • VS/MSVC:虚函数表放在常量区 (.rdata 只读段),属于程序常量,运行时不可修改。

  • g++:同样放置只读数据段。 虚函数本身的函数本体,存放在代码段.text虚表只存虚函数的指针地址

注意区分:

  • _vfptr每个对象里面的成员变量,跟着对象走(栈 / 堆看对象在哪创建)

  • 虚表_vftable:全局只读常量,程序启动就存在。

五、常见考点总结

  1. 只要类有虚函数,对象开头增加虚表指针;虚表多个对象共享一张。

  2. 派生类重写虚函数:派生类虚表对应下标位置替换为自己函数地址。

  3. 多态三要素缺一不可:

    1. ①父类函数 virtual 虚函数

    2. ②派生类完成重写 override

    3. ③使用父类指针 / 引用接收子类对象

  4. 满足多态:运行查表;不满足:编译期直接调用。

  5. 虚表属于只读常量区;虚表指针是对象成员,随对象存储位置变化。

六、易踩坑选择题回顾

class Base { public: virtual void Func1() { cout << "Func1()" << endl; } protected: int _b = 1; char _ch = 'x'; }; int main() { Base b; cout << sizeof(b) << endl; return 0; }

32 位编译器运行结果:12

  • 虚表指针 4 + int4 + char1 + 内存对齐填充 3 = 12。


补充:链接报错 LNK2019 和虚函数小提示

截图出现 LNK2019 无法解析外部符号:

  • 如果只写虚函数声明,没有写函数实现就会报链接错误。

virtual void func(int); //只有声明,无定义 → LNK2019

如果不想实现,写成纯虚函数virtual void func(int) = 0;

http://www.jsqmd.com/news/1401595/

相关文章:

  • 2026 年临沧口碑好的非晶合金油浸式变压器供应商推荐几家,你家工厂在用的这款“电老虎”,竟能悄悄帮你省下近三成电费? - 企业推荐管【认证】
  • 2026年8月滨州市电信1000M单宽带怎么安装 - 找卡家园
  • 智读致用《反向学习》04|德鲁克说:你的注意力,是你唯一真正拥有的稀缺资源
  • DeepSeek Harness安装使用初体验
  • 2026 年更新:海淀正规的窨井清理公司施工队推荐几家,别等路面塌陷才想起,原来这类民生服务竟藏着这么多门道。 - 行业推荐官-2
  • 2026年8月口碑好的无刷风机采购指南 靠谱合作厂家选宁波东莱机电 - 起跑123
  • 2026 年 8 月新发布:海盐知名的冷冻柜回收服务团队怎么联系,家里堆的旧冷柜没人管?转手竟能换半箱油,这招好多人都不知道-博霄制冷设备回收 - 企业信息推荐-2
  • 国内合规代理记账公司盘点:5家实力机构一览 - 起跑123
  • 慢慢整理一下用到的游戏相关工具
  • 2026年小型液压系统选购评测 宁波帕尼科液压技术有限公司详解 - 起跑123
  • 2026年8月武汉市蔡甸区移动1000M单宽带怎么选不踩坑一篇说透 - 找卡家园
  • 企业自动化运营-电子合同预览-东方仙盟
  • Landsat遥感波段全解析:从TM到OLI/TIRS的实战应用指南
  • 2026年8月滨州市电信500M单宽带怎么办理 - 找卡家园
  • 2026 年广州有实力的女装公司哪家专业,38度天还想捂着出门?这玩意儿的防晒力比你想的狠多了 - 行业推荐官【认证】
  • FreeRTOS调度器:让多任务有条不紊的“大管家”
  • 2026推荐 无刷风机靠谱厂家选型指南 宁波东莱机电深度解析 - 起跑123
  • 2026年黑龙江新能源暖通光伏设备实测参考指南,太阳能、空气能、中央空调、光伏、采暖设备、热水设备 - 海棠依旧大
  • # UC1617S 液晶随机卡死 —— 排查笔记
  • 学校指定知网检测完整攻略!低成本自查论文AI率是否达标!
  • SEO内容优化实战:选题策略与用户意图解析
  • 2026年8月正规的佛山制造建星肌肤釉瓷砖厂家怎么选测评,肌肤釉岩板系列厂家分析 - 海棠依旧大
  • 2026年新能源暖通光伏公司实力选择指南:哈尔滨卓茂达新能源科技有限公司一站式方案助力北方清洁能源改造 - 海棠依旧大
  • 2026年8月济南市天桥区广电1000M单宽带怎么选不踩坑 - 找卡家园
  • 2026年高端模具厂热流道系统高性价比厂家测评指南 - 起跑123
  • 2026推荐海淀优秀的艾比森LED显示屏厂家哪家靠谱 - 装修教育财税推荐2026
  • 2026年8月滨州市电信300M单宽带小白避坑指南 - 找卡家园
  • 2026年龙门式全自动影像测量仪热门厂家对比测评 - 起跑123
  • 2026年热流道选购评测 宁波甬宁模具科技有限公司优势解析 - 起跑123
  • 2026 年更新:温州专业的玻璃钢盖板生产厂家找哪家,踩过市政工程坑的人,都懂选对这玩意儿能省多少心 - 行业严选官