谈虚函数表
2021-08-27 本文已影响0人
404Not_Found
- 作者: 雪山肥鱼
- 时间:20210825 22:50
- 目的: 理解虚函数表
虚函数的位置
虚函数并没有固定位置,应该与编译器有关。在vs2017 和 g++ 下,处于对象开头
#include <iostream>
using namespace std;
class A
{
public:
int i; //4字节
virtual void testfunc() {} //虚函数,vptr4字节。
};
int main()
{
//虚函数表指针位置分析
//类:有虚函数,这个类会产生一个虚函数表。
//类对象,有一个指针,指针(vptr)会指向这个虚函数表的开始地址。
A aobj;
int ilen = sizeof(aobj);
cout << ilen << endl; //8字节
char *p1 = reinterpret_cast<char *>(&aobj); //类型转换,硬转 &aobj这是对象aobj的首地址。
char *p2 = reinterpret_cast<char *>(&(aobj.i));
if (p1 == p2) //说明aobj.i和aobj的位置相同,说明i在对象aobj内存布局的上边。虚函数表指针vptr在下边
{
cout << "虚函数表指针位于对象内存的末尾" << endl;
}
else
{
cout << "虚函数表指针位于对象内存的开头" << endl;
}
return 1;
}
输出均在对象内存开头
继承关系下的虚函数表
#include <iostream>
#include <stdio.h>
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; }
};
class Derive :public Base
{
public:
virtual void g() {cout << "Base::g()" <<endl; }
};
int main(int argc,char **argv)
{
cout << sizeof(Base) <<endl;
cout << sizeof(Derive)<< endl;
cout << "---------Derive-----------" << endl;
Derive *d = new Derive();
long * pvptr = (long*)d;
long * vptr = (long*)*pvptr;
for (int i = 0; i <= 4; i++)
{
printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
}
typedef void (*Func)(void);
Func f = (Func)vptr[0];
Func g = (Func)vptr[1];
Func h = (Func)vptr[2];
f();
g();
h();
cout << "------Base-----------" << endl;
Base *dpar = new Base();
long *pvptrpar = (long *)dpar;
long *vptrpar = (long *)(*pvptrpar);
for (int i = 0; i <= 4; i++) //循环5次;
{
printf("vptr Base[%d] = 0x:%p\n", i, vptrpar[i]);
}
Func fpar = (Func)vptrpar[0];
Func gpar = (Func)vptrpar[1];
Func hpar = (Func)vptrpar[2];
fpar();
gpar();
hpar();
return 0;
}
运行结果.png
父子函数虚函数表的关系:
父子虚函数表的关系.png
代码说明:
- 取出函数指针的操作,只是想拿到虚函数表的每隔元素,注意是vtable, 而不是varray, 是table, 不是数组,说明里面的函数指针类型是可以不同的。
所以用long 去取,只是为了步长是4, 拿到元素而已。 - 父类一张表A, 子类一张表B, 子类自定义的虚函数,会更新虚函数表里的函数地址,其他不变,继承下来。
- 如果子类没有重新任何虚函数,则会拷贝一份父类的虚函数表。两者内容相同,但是在内存的不同位置。
- 超出虚函数表的内容暂定为不可知
虚表在继承关系之间赋值的情况分析
#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; }
};
class Derive :public Base
{
virtual void g() { cout << "Derive::g()" << endl; }
};
typedef void(*Func)(void);
int main(int argc, char ** argv)
{
Derive derive;
long * pvfbderive = (long*)&derive;
long * pvptrderive = (long*)(*pvfbderive);
//pvptrderive = 0x00289b6c {Project1.exe!void(* Derive::`vftable'[4])()} {2625911}
Func f1 = (Func)pvptrderive[0]; //f1 = 0x00281177 {Project1.exe!Base::f(void)}
Func f2 = (Func)pvptrderive[1]; //f2 = 0x002812cb {Project1.exe!Derive::g(void)}
Func f3 = (Func)pvptrderive[2]; //f3 = 0x002812df {Project1.exe!Base::h(void)}
Func f4 = (Func)pvptrderive[3];
Func f5 = (Func)pvptrderive[4];
f1();
f2();
f3();
Base base = derive;//生成base对象,用derive 初始化base对象,编译器直接切割derive,把属于base的对象的内容的内容拷贝给对象base。
// 子对象的 虚函数表指针的值,并没有覆盖 base对象的 虚函数表指针值
long * pvfbBase = (long*)&base;
long * pvptrBase = (long*)(*pvfbBase);
//pvptrBase = 0x00289b34 {Project1.exe!void(*Base::`vftable'[4])()} {2625911}
Func b1 = (Func)pvptrBase[0];//b1 = 0x00281177 {Project1.exe!Base::f(void)}
Func b2 = (Func)pvptrBase[1];//b2 = 0x0028114f {Project1.exe!Base::g(void)}
Func b3 = (Func)pvptrBase[2];//b3 = 0x007812df {Project1.exe!Base::h(void)}
Func b4 = (Func)pvptrBase[3];
Func b5 = (Func)pvptrBase[4];
Base * pBase = new Derive();
long * ppvfbBase = (long*)pBase;
long * ppvptrBase = (long*)(*ppvfbBase);
Func bb1 = (Func)ppvptrBase[0];//bb1 = 0x0046119f {Project1.exe!Base::f(void)}
Func bb2 = (Func)ppvptrBase[1];//bb2 = 0x00461311 {Project1.exe!Derive::g(void)}
Func bb3 = (Func)ppvptrBase[2];//bb3 = 0x00321325 {Project1.exe!Base::h(void)}
Base & rBase = derive;
long * rpvfbBase = (long*)&rBase;
long * rpvptrBase = (long*)(*rpvfbBase);
Func rb1 = (Func)rpvptrBase[0]; //rb1 = 0x004d119f {Project1.exe!Base::f(void)}
Func rb2 = (Func)rpvptrBase[1]; //rb2 = 0x004d1311 {Project1.exe!Derive::g(void)}
Func rb3 = (Func)rpvptrBase[2]; //rb3 = 0x004d1325 {Project1.exe!Base::h(void)}
b2();
return 0;
父类对象 = 子类对象
由注释结果可知,这种情况,子类对象的虚函数表指针并没有覆盖父类对象的虚函数表指针,父类对象自己用自己的虚函数表
所以此时并不存在多态说法
父类对象指针 = 子类对象地址
子类虚函数表指针覆盖覆盖 父类虚函数表指针
父类对象引用 = 子类对象
子类虚函数表指针覆盖覆盖 父类虚函数表指针
多重继承中的虚函数表分析
using namespace std;
class Base1 {
public:
virtual void f()
{
cout << "base1::f()" << endl;
}
virtual void g()
{
cout << "base1::g()" << endl;
}
};
class Base2 {
public:
virtual void h()
{
cout << "base2::f()" << endl;
}
virtual void i()
{
cout << "base2::g()" << endl;
}
};
class Derived:public Base1, public Base2
{
public:
virtual void f()
{
cout << "derived::f()" << endl;
}
virtual void i()
{
cout << "derived::i()" << endl;
}
virtual void mh()
{
cout << "derived::mh()" << endl;
}
virtual void mi()
{
cout << "derived::mi()" << endl;
}
virtual void mj()
{
cout << "derived::mj()" << endl;
}
};
int main(int argc, char ** argv)
{
cout << sizeof(Base1) << endl;
cout << sizeof(Base2) << endl;
cout << sizeof(Derived) << endl;
Derived ins;
Base1 &b1 = ins;
Base2 &b2 = ins;
Derived &d = ins;
typedef void(*Func)(void);
long * pderived1 = (long*)(&ins);
long * vptr1 = (long*)(*pderived1);
long * pderived2 = pderived1 + 1;
long * vptr2 = (long*)(*pderived2);
Func f1 = (Func)vptr1[0]; //f1 = 0x0082122b {Project1.exe!Derived::f(void)}
Func f2 = (Func)vptr1[1]; //f2 = 0x008213a7 {Project1.exe!Base1::g(void)}
Func f3 = (Func)vptr1[2]; //f3 = 0x00821177 {Project1.exe!Derived::mh(void)}
Func f4 = (Func)vptr1[3]; //f4 = 0x00821438 {Project1.exe!Derived::mi(void)}
Func f5 = (Func)vptr1[4]; //f5 = 0x00821325 {Project1.exe!Derived::mj(void)}
Func f6 = (Func)vptr1[5]; //f6 = 0x00000000
Func f7 = (Func)vptr1[6]; //f7 = 0x0082a938 {Project1.exe!const Derived::`RTTI Complete Object Locator'{for `Base2'}
Func f11 = (Func)vptr2[0]; //f11 = 0x008210dc {Project1.exe!Base2::h(void)}
Func f12 = (Func)vptr2[1]; //f12 = 0x00821131 {Project1.exe!Derived::i(void)}
Func f13 = (Func)vptr2[2]; //f13 = 0x00000000
Func f14 = (Func)vptr2[3]; //f14 = 0x69726564
b1.f();
b2.i();
d.f();
d.i();
d.mh();
d.g();//故意没有重新 base1的虚函数 g
return 0;
}
由上述结果可知
- 子类 继承两个父类,则会有两个虚函数表指针,指向两个虚函数表
- 子类 和 第一个父类 共用一个虚函数表,也就是说,这个表里面有父类一(重写或未重写)的表, 也有 保存了子类自己的虚函数
- 对于未被重写的 虚函数 g(), 子类依旧使用父类的虚函数,因为函数表中的该项指向的函数不变。
虚函数表和虚函数指针是何时创建的
vs2017 下可以在开发人员模式先用以下命令查看对象结构:
cl /dl reportSingleClassLayoutDerived main.cpp
结构.png
对象结构与上一节中的对象分析截图相同
linux 下 用
g++ -fdump-class-hierarchy -fsyntax-only main.cpp
结构.png
- 对象什么时候创建出来,虚函数指针就何时创建
实际上通过之前章节了解到,对象创建调用构造函数,会自己往构造函数塞代码,给vptr赋值 - 虚函数表的创建时机
在编译阶段就已经准备好了,其实就在代码段啦。