总结:虚函数表是针对类的,一个类的所有对象的虚函数表都一样。
虚指针(vptr):每个含有虚方法(虚函数)对象里有虚表指针,指向虚表。
虚函数表:虚函数表是顺序存放虚函数地址的,虚表是顺序表,表里存放了虚函数的地址。
C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。目前gcc 和微软的编译器都是将vptr放在对象内存布局的最前面。
【代码示例】
#include <iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
typedef void(*Fun)(void);
int main()
{
Fun pFun = NULL;
Base obj_1,obj_2;
// obj_1 虚函数表 — 第一个函数
pFun = (Fun)*((int*)*(int*)(&obj_1));
pFun();
pFun = (Fun)*(((int*)*(int*)(&obj_1))+1);
pFun();
// 输出 虚函数表地址 与 虚函数表元素 的值
cout << "obj_1 虚函数表地址:" << (int*)(&obj_1) << endl;
cout << "obj_2 虚函数表地址:" << (int*)(&obj_2) << endl;
cout << "obj_1 虚函数表 — 第一个函数地址:" << (int*)*(int*)(&obj_1) << endl;
cout << "obj_2 虚函数表 — 第一个函数地址:" << (int*)*(int*)(&obj_2) << endl;
return 0;
}
/*
输出 :
Base::f
Base::g
obj_1 虚函数表地址:0018FF40
obj_2 虚函数表地址:0018FF3C
obj_1 虚函数表 — 第一个函数地址:0046F0AC
obj_2 虚函数表 — 第一个函数地址:0046F0AC
Press any key to continue
*/
【结论】
- 虚函数表属于类,类的所有对象共享这个类的虚函数表。
- 不同对象虚函数表是一样的(虚函数表的第一个函数地址相同);
- 每个对象内部都保存一个指向该类虚函数表的指针vptr,每个对象的vptr的存放地址都不一样,但是都指向同一虚函数表。
那么虚函数表存放在哪里呢?
虽然我们知道vptr指向虚函数表,那么虚函数表具体存放在内存哪个位置呢,虽然这里我们已经可以得到虚函数表的地址。实际上虚函数指针是在构造函数执行时初始化的,而虚函数表是存放在可执行文件中的。
下面的一篇博客测试了微软的编译器将虚函数表存放在了目标文件或者可执行文件的常量段中,http://blog.csdn.net/vicness/article/details/3962767
在gcc编译器的实现中虚函数表vtable存放在可执行文件的只读数据段.rodata中。
虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别。
参考自https://blog.csdn.net/w616589292/article/details/51250285