vtable and vptr
有了虚函数以后,对象所占用的存储空间比没有虚函数时多了 4 个字节。实际上,任何有虚函数的类及其派生类的对象都包含这多出来的 4 个字节,这 4 个字节就是实现多态的关键——它位于对象存储空间的最前端,其中存放的是虚函数表的地址。
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表(vtable),该类的任何对象中都放着该虚函数表的指针(可以认为这是由编译器自动添加到构造函数中的指令完成的)。
假设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:
-
取出 pa 指针所指位置的前 4 个字节,即对象所属的类的虚函数表的地址(在 64 位编译模式下,由于指针占 8 个字节,所以要取出 8 个字节)。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;如果 pa 指向的是类 B 的对象,则这个地址就是类 B 的虚函数表的地址。
-
根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。不妨认为虚函数表是以函数名作为索引来查找的,虽然还有更高效的查找方法。
如果 pa 指向的是类 A 的对象,自然就会在类 A 的虚函数表中查出 A::func 的地址;如果 pa 指向的是类 B 的对象,就会在类 B 的虚函数表中查出 B::func 的地址。
类 B 没有自己的 func2 函数,因此在类 B 的虚函数表中保存的是 A::func2 的地址,这样,即便 pa 指向类 B 的对象,pa->func2();这条语句在执行过程中也能在类 B 的虚函数表中找到 A::func2 的地址。 -
根据找到的虚函数的地址调用虚函数。
由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。
多态机制能够提高程序的开发效率,但是也增加了程序运行时的开销。虚函数表、各个对象中包含的 4 个字节的虚函数表的地址都是空间上的额外开销;而查虚函数表的过程则是时间上的额外开销。
vptr在创建类实例时自动设置,以便指向该类的虚拟表。与this指针不同,this指针实际上是编译器用来解析自引用的函数参数,vptr是一个真正的指针。因此,它使每个类对象的分配大一个指针的大小。这也意味着vptr由派生类继承.所有同类对象,共享一个vtable,但是每个对象都自带一个vptr指向这个vtable,否则调用虚函数的时候会找不到正确的函数入口
如果父类的虚函数没有被子类改写, 那么子类的虚函数表中的元素就是父类的对应的虚函数指针;相反,如果子类改写了父类的虚函数,那么对应的虚函数表中的元素就是自己的虚函数指针,决议这个指向的过程发生在运行时,就是所谓的动态绑定.
Virtual *p = new Entity(1, 2);
void* vptr=(void*)*(unsigned long*)p;//获取vptr
//提取指针指向位置存储的前八个字节,unsigned long 可以写作int64_t
void* fun_addr=(void*)*((int64_t)vptr+offset)//从vtable截取函数地址,offset 取0,1,2...,
vtable:
0x555555558ca8(vptr) <_ZTV6Entity+16>: 0xa2 0x67 0x55 0x55 0x55 0x55 0x00 0x00//~Entity()
0x555555558cb0 <_ZTV6Entity+24>: 0xd0 0x67 0x55 0x55 0x55 0x55 0x00 0x00//~Entity()
0x555555558cb8 <_ZTV6Entity+32>: 0x36 0x66 0x55 0x55 0x55 0x55 0x00 0x00//func()
0x555555558cc0 <_ZTV6Entity+40>: 0x90 0x66 0x55 0x55 0x55 0x55 0x00 0x00//func2()
0x555555558cc8 <_ZTV6Entity+48>: 0xcc 0x66 0x55 0x55 0x55 0x55 0x00 0x00
0x555555558cd0 <_ZTV7Virtual>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x555555558cd8 <_ZTV7Virtual+8>: 0x38 0x8d
0x5555555567a2 <Entity::~Entity()>: 0xf3 0x0f 0x1e 0xfa 0x55 0x48 0x89 0xe5
0x5555555567aa <Entity::~Entity()+8>: 0x48 0x83 0xec 0x10 0x48 0x89 0x7d 0xf8
0x5555555567b2 <Entity::~Entity()+16>: 0x48 0x8d 0x15 0xef 0x24 0x00 0x00 0x48
0x5555555567ba <Entity::~Entity()+24>: 0x8b 0x45 0xf8 0x48 0x89 0x10 0x48 0x8b
0x5555555567c2 <Entity::~Entity()+32>: 0x45 0xf8 0x48 0x89 0xc7 0xe8 0x16 0xfd
0x5555555567ca <Entity::~Entity()+40>: 0xff 0xff 0x90 0xc9 0xc3 0x90 0xf3 0x0f
0x5555555567d2 <Entity::~Entity()+2>: 0x1e 0xfa
0x555555556690 <Entity::func2()>: 0xf3 0x0f 0x1e 0xfa 0x55 0x48 0x89 0xe5
0x555555556698 <Entity::func2()+8>: 0x48 0x83 0xec 0x10 0x48 0x89 0x7d 0xf8
0x5555555566a0 <Entity::func2()+16>: 0x48 0x8d 0x35 0x78 0x09 0x00 0x00 0x48
0x5555555566a8 <Entity::func2()+24>: 0x8d 0x3d 0x92 0x29 0x00 0x00 0xe8 0x5d
0x5555555566b0 <Entity::func2()+32>: 0xfa 0xff 0xff 0x48 0x89 0xc2 0x48 0x8b
0x5555555566b8 <Entity::func2()+40>: 0x05 0x13 0x29 0x00 0x00 0x48 0x89 0xc6
0x5555555566c0 <Entity::func2()+48>: 0x48 0x89
0x555555556636 <Entity::func()>: 0xf3 0x0f 0x1e 0xfa 0x55 0x48 0x89 0xe5
0x55555555663e <Entity::func()+8>: 0x48 0x83 0xec 0x10 0x48 0x89 0x7d 0xf8
0x555555556646 <Entity::func()+16>: 0x48 0x8b 0x45 0xf8 0x8b 0x40 0x08 0x89
0x55555555664e <Entity::func()+24>: 0xc6 0x48 0x8d 0x3d 0xea 0x29 0x00 0x00
0x555555556656 <Entity::func()+32>: 0xe8 0x25 0xfb 0xff 0xff 0xbe 0x20 0x00
0x55555555665e <Entity::func()+40>: 0x00 0x00 0x48 0x89 0xc7 0xe8 0xf8 0xfa
0x555555556666 <Entity::func()+48>: 0xff 0xff