前言
在 C++ 中,多态是面向对象编程的核心特性之一。很多同学在学习时会有这些疑问:
- 为什么带虚函数的类,
sizeof大小会多出 4/8 字节? - 基类指针指向不同派生类对象,是如何在运行时找到对应函数的?
- 虚表、虚指针、虚函数分别存在内存哪个区域?
- 静态绑定和动态绑定到底有什么区别?
本篇就从内存布局、对象模型、汇编视角、虚表结构出发,把 C++ 多态的底层原理彻底讲透。
一、虚函数和普通函数的区别
下面我们通过一道题来阐明这个问题:
下面编译为 32 位程序的运行结果是什么? A. 编译报错 B. 运行报错 C. 8 D. 12
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;
}
正常对于一个类来说,它的成员函数所占内存总和再内存对齐之后就是其类内存大小。我们试着来看一下这个带虚函数类的内存大小:
12
正常来说是 1+5(char + int)然后内存对齐为 8,可是其运行结果是 12,为什么会这样呢?
我们监视窗口调试一下,会发现对象内部多出了一个 _vfptr。没错,这个指针叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中至少都有一个虚函数表指针,因为一个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表。
二、多态的原理
多态是如何实现的
从底层的角度 Func 函数中 ptr->BuyTicket(),是如何作为 ptr 指向 Person 对象调用 Person::BuyTicket,ptr 指向 Student 对象调用 Student::BuyTicket 的呢?
通过下图可以看到,满足多态条件后,底层不再是编译时通过调用对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派生类就调用派生类对应的虚函数。
第一张图,ptr 指向的 对象,调用的是 的虚函数;第二张图, 指向的 对象,调用的是 的虚函数。


