背景
虚函数表:它是一个虚函数地址的数组,一个类中可能有多个虚函数,这些虚函数会构成一个数组,虚函数表就是这个数组,记录虚函数地址,指向.text中具体代码的一个位置
作用:用来实现多态(一个接口不同形态):
静态多态(编译期确定)
动态多态(虚继承重写,运行时才确定调用哪个函数,基类指针指向的具体对象类型是子类还是基类?如果是基类,从基类虚函数表找函数指针;如果是子类,从子类虚函数表找函数指针
虚函数表创建前提
编译过程中,发现某个类有virtual关键字,修饰的函数,会生成虚函数表
1、什么时候生成?
编译器编译的时候生成,发现virtua关键字修饰的函数
2、存放在哪里?
.o文件 磁盘中在只读数据段中(,rodata),运行当中只在只读代码区域里
.bss:未初始化的或者初始化为0的全局静态变量
.data:已初始化的全局、静态变量放在数据区
.rodata:只读数据段,虚函数表,运行时放在代码区(虚函数地址的数组)
运行时,.bss和.data会加载到静态存储区
虚函数表与虚函数指针的关系:
虚函数表指针就是指向虚函数表的指针
class A{
public:
virtual void func();
};
这个类前面4个字节(32位,64位是8个字节)对应存储的是我们这个虚函数表指针(vptr),在堆区,vptr指向我们虚函数表地址,这个虚函数表(函数地址)数组里的指针,指着具体代码段中的地址
两者关系:
每个类只有一个虚函数表
vptr都是在堆上分配,指向的内容是一样的,地址肯定不一样(浅拷贝可能相同,共享一个指针(地址相同,释放有危险);深拷贝会在另外堆区开辟一个指针(地址不同),指向相同的虚函数表地址)
类的不同对象,通常虚函数表的指针不一样的,拷贝构造或重载赋值运算符
虚函数表指针创建的时机:
1.类对象构造时,在构造函数时,编译器把类的虚函数表的地址赋给vptr
2.如果没有构造函数,类中有虚函数表,编译器会为类生成默认的构造函数,为这个类对象在堆前面生成8个字节指向虚函数表指针,构造函数的作用就是把类的虚函数表地址复制给我们的vptrv
3、继承情况下,虚函数表指针赋值过程
a.调用基类构造函数,把A的虚函数表的地址赋值给vptr
b.调用子类构造函数,把B的虚函数表的地址赋值给vptr