- 虚函数表并不依赖对象而存在
虚函数表是在编译期就确定的,意味着在可执行文件或动态库文件中是固定存在的,
如果打印虚函数表的地址,可以发现每次都是一致的(需关闭ALSR功能,参https://www.cnblogs.com/csnd/p/11800531.html)
这是 “虚函数表并不依赖对象而存在” 成立的原因,
测试代码如下:
class A
{
public:
virtual void func(){puts("this is virtual func");}
};
typedef void (*pfunc_t)();
int main()
{
A * p = new A;
pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)p));
delete p;
p2();
return 0;
}
测试结果:
可以正常调用调用func函数,而这之前p对象已经释放了(注意:如果函数中访问了类成员变量,就不能这么调了)
对main中第二句的解释:
(int *)p 是将p解释为指向int(4字节)的指针,
*(int*)p 取到的是p指向的对象的前4字节的内容(指向需表的地址)
(int*)(*(int*)p) 把虚表的地址解释为指向int的指针
*(int*)(*(int*)p) 取出虚表的前4个字节的内容(指向第一个虚函数)
- 避免在构造/析构函数中调用虚函数,因为这通常并不能达到你所需要的效果
构造函数中,因为基类构造函数先被调用,
基类构造函数执行期间,会把前4个字节首先指向基类的虚函数表,
等到派生类构造函数调用时,才会把前4个字节更改为派生类的虚函数表,
所以在基类构造函数中调用的虚函数,不会是派生类的虚函数,而是自己的虚函数
析构函数链的调用顺序则是派生类析构函数先被调用,然后是基类析构函数
派生类析构函数调用完成之后,基类析构函数会把对象的前4个字节重新指向基类的虚函数表
所以在基类中调用的虚函数,也将会使基类自己的虚函数,而不是派生类中的虚函数。
试验:
class A
{
public:
A()
{
printf("this = %p\n",this);
printf("this 前4字节的值为 %d\n",*(int*)this);
pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this));
p2();
}
~A()
{
printf("this = %p\n",this);
printf("this 前4字节的值为 %d\n",*(int*)this);
pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this));
p2();
}
virtual void func()
{
puts("this is v func in A");
}
};
class B : public A
{
public:
B(){
printf("this = %p\n",this);
printf("this 前4字节的值为 %d\n",*(int*)this);
pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this));
p2();
}
~B(){
printf("this = %p\n",this);
printf("this 前4字节的值为 %d\n",*(int*)this);
pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this));
p2();
}
virtual void func()
{
puts("this is v func in B");
}
};
int main()
{
B * p = new B;
delete p;
return 0;
}
执行结果
this = 00462320
this 前4字节的值为 3772236 //这个值是基类虚函数表的位置
this is v func in A
this = 00462320
this 前4字节的值为 3771628 //这个值是派生类虚函数表的位置
this is v func in B
this = 00462320
this 前4字节的值为 3771628 //这个值是派生类虚函数表的位置
this is v func in B
this = 00462320
this 前4字节的值为 3772236 //这个值是基类虚函数表的位置
this is v func in A