GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • 卡码笔记-最强八股文
  • 计算机基础
  • Python
  • C++
  • Java
  • Go
  • ChatGPT充值
  • Claude充值
  • 国内使用Claude
  • 中转站推荐
  • 🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经

This view

  • Original page
  • New address

GeezerVison is showing notes.kamacoder.com — C++虚函数实现机制:vtable虚函数表、vptr虚指针、动态绑定调用过程详解

# 虚函数的实现机制

面试官问:"虚函数是怎么实现的?调用一个虚函数的时候底层发生了什么?"

这题考的不是"什么是多态",而是多态背后的实现细节。很多人能说出"虚函数表"三个字就卡住了,说不清vtable在哪、vptr什么时候初始化、调用链路是怎么走的。把这条链路讲清楚,这题就拿满分。

# 简要回答

编译器为每个含虚函数的类生成一张虚函数表(vtable),表里存的是该类各虚函数的地址。每个对象内部有一个隐藏的虚指针(vptr),指向所属类的vtable。通过基类指针调用虚函数时,运行时沿着 对象 → vptr → vtable → 函数地址 这条链路找到真正要调用的函数,这就是动态绑定。

# 详细回答

vtable:每个类一张表

编译器看到一个类里有virtual函数,就会为这个类生成一张vtable。表里按声明顺序存放各虚函数的地址。派生类如果override了某个虚函数,它的vtable里对应位置就换成自己的函数地址;没override的槽位继续指向基类版本。

vptr:每个对象一个指针

每个含虚函数的对象,内存布局里都多一个隐藏成员——vptr。构造函数执行时,编译器自动把vptr设置为指向当前类的vtable。所以Base* p = new Derived(),p指向的对象里的vptr指向的是Derived的vtable,不是Base的。

调用链路

p->show() 在底层展开为:取对象的vptr → 在vtable中按偏移找到show的地址 → 跳转调用。整个过程多了一次指针解引用(间接寻址),这就是虚函数比普通函数调用慢一点的原因。

核心就一句话:vtable让"调哪个函数"变成了运行时查表,而不是编译时写死。

虚函数实现机制

# 知识拓展

面试官可能追问:

Q1: vptr放在对象的什么位置?

主流实现(GCC/Clang/MSVC)都把vptr放在对象内存的最前面,这样通过对象首地址就能直接拿到vptr,不需要额外偏移计算。

Q2: 多继承时vtable怎么处理?

多继承时一个对象可能有多个vptr,每条继承链一个,对应多张vtable。通过不同基类指针调用虚函数时,走的是不同的vptr和vtable。

Q3: 虚函数调用比普通函数慢多少?

多一次指针解引用,通常就是一条mov加一条间接call的开销。现代CPU有分支预测和缓存,实际性能差距很小。真正的代价是编译器无法内联虚函数调用,丧失了内联优化的机会。

Q4: 为什么基类析构函数要声明为virtual?

delete basePtr时,如果析构函数不是virtual,只会调用基类的析构函数,派生类的析构不会执行,派生类申请的资源就泄漏了。声明为virtual后,析构也走vtable查表,能正确调到派生类析构。

← C++中的虚函数和纯虚函数有什么区别? 多重继承的优缺点及菱形继承问题 →

评论

验证登录状态...

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.