6月1日 10:35
C++ 虚函数底层怎么实现?vtable/vptr 与动态绑定原理
C++虚函数通过虚函数表(vtable)和虚指针(vptr)实现运行时多态。每个含有虚函数的类,编译器会为其生成一个vtable——一个静态数组,按声明顺序存放该类所有虚函数的函数指针。每个对象实例在内存布局的起始位置持有一个vptr,指向所属类的vtable。调用虚函数时,程序通过vptr找到vtable,再从表中取出对应槽位的函数指针进行间接调用,这就实现了动态绑定。单继承下对象只有一个vptr,多继承下会有多个vptr,分别指向不同基类的vtable。虚函数调用的开销主要是一次额外的间接寻址,现代CPU分支预测能大部分消除这个代价。纯虚函数在vtable中通常放入一个抛出异常的纯虚调用占位函数,防止误调。析构函数声明为虚函数是必要的——否则通过基类指针delete派生类对象时,只会调用基类析构函数,派生类资源泄漏。
追问
vtable存放在内存的哪个区域?
vtable是编译期生成的只读数据,通常放在.rodata段,同一类的所有对象共享同一个vtable。
构造函数可以是虚函数吗?
不行。构造时对象类型尚未完整,vptr还未指向最终类的vtable。构造函数执行期间,vptr逐步从基类向派生类更新,此时虚函数机制尚未就绪。
虚函数调用和普通函数调用性能差距大吗?
单次间接寻址开销约1-2个CPU周期,现代分支预测器命中率极高,实际差距可忽略。真正影响性能的是虚函数阻碍了编译器内联优化。
为什么析构函数必须虚函数?
通过基类指针delete派生类对象时,若析构非虚,只调用基类析构函数,派生类析构不执行,资源泄漏。这是C++最常见的内存泄漏来源之一。