虚函数表的特征
- 虚函数表是全局共享的元素, 全局仅有1个, 在编译时构造完成
- 虚函数表类似一个数组, 类对象中存储vptr指针, 指向虚函数表。即虚函数表不是函数,不是程序代码,所以不可能存储在代码段
- 虚函数表存储虚函数的地址,也就是说虚函数表的元素是指向类成员函数的指针, 而类中虚函数的个数在编译期就可以确定,所以虚函数表的大小在编译期可以确定的,不必动态分配内存空间存储虚函数表,所以不在堆中
总结: 虚函数表类似于类中的静态成员变量,和静态成员变量一样是全局共享的,属于一个类所有对象的,并不是某一个类特属的, 在linux/unix中 存放在可执行文件的只读数据段中
对于有虚函数或者继承于有虚函数的基类时,对这个类实例化的时候,构造函数执行时会对虚函数表指针vptr进行初始化,gcc和微软的的编译器会将其放在对象内存布局的最前面
有虚函数的类, 改类的大小会增加一个指针的大小(32位系统是4字节, 64位系统是8字节)。
#include <iostream>
#include <stdio.h>
using namespace std;
class Base {
public:
virtual void a() { cout << "Base a()" << endl; }
virtual void b() { cout << "Base b()" << endl; }
virtual void c() { cout << "Base c()" << endl; }
};
class Derive : public Base {
public:
virtual void b() { cout << "Derive b()" << endl; }
};
int main()
{
typedef void (*Func)();
cout << "-----------Base------------" << endl;
Base* q = new Base;
long* tmp1 = (long*)q;
long* vptr1 = (long*)(*tmp1);
for (int i = 0; i < 3; i++) {
printf("vptr[%d] : %p\n", i, vptr1[i]);
}
Func a = (Func)vptr1[0];
Func b = (Func)vptr1[1];
Func c = (Func)vptr1[2];
a();
b();
c();
Derive* p = new Derive;
long* tmp = (long*)p;
long* vptr = (long*)(*tmp);
cout << "---------Derive------------" << endl;
for (int i = 0; i < 3; i++) {
printf("vptr[%d] : %p\n", i, vptr[i]);
}
Func d = (Func)vptr[0];
Func e = (Func)vptr[1];
Func f = (Func)vptr[2];
d();
e();
f();
return 0;
}
多重继承
多重继承的派生类, 会包含多个虚函数指针。
注意,派生类将自己的虚函数放在base1的虚函数表后面
虚函数表在编译时期创建, 而虚函数指针是在对象实例化时候创建(调用构造函数时), 所以 **虚函数指针在运行时创建 ** , 将该类虚函数表的地址赋值给对象的虚函数指针。
一般分为五个区域: 栈区, 堆区,函数区(存放函数体等二进制代码), 全局静态区, 常量区。
所以C++中虚函数表 位于只读数据段(.rodata), 也就是C++模型中的常量区, 而虚函数则位于代码段(.text) ,也就是内存模型中的代码区
参考: