关于虚表

vtable and vptr

关于虚表

有了虚函数以后,对象所占用的存储空间比没有虚函数时多了 4 个字节。实际上,任何有虚函数的类及其派生类的对象都包含这多出来的 4 个字节,这 4 个字节就是实现多态的关键——它位于对象存储空间的最前端,其中存放的是虚函数表的地址。

每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表(vtable),该类的任何对象中都放着该虚函数表的指针(可以认为这是由编译器自动添加到构造函数中的指令完成的)。

假设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:

  1. 取出 pa 指针所指位置的前 4 个字节,即对象所属的类的虚函数表的地址(在 64 位编译模式下,由于指针占 8 个字节,所以要取出 8 个字节)。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;如果 pa 指向的是类 B 的对象,则这个地址就是类 B 的虚函数表的地址。

  2. 根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。不妨认为虚函数表是以函数名作为索引来查找的,虽然还有更高效的查找方法。
    如果 pa 指向的是类 A 的对象,自然就会在类 A 的虚函数表中查出 A::func 的地址;如果 pa 指向的是类 B 的对象,就会在类 B 的虚函数表中查出 B::func 的地址。
    类 B 没有自己的 func2 函数,因此在类 B 的虚函数表中保存的是 A::func2 的地址,这样,即便 pa 指向类 B 的对象,pa->func2();这条语句在执行过程中也能在类 B 的虚函数表中找到 A::func2 的地址。

  3. 根据找到的虚函数的地址调用虚函数。

由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。

多态机制能够提高程序的开发效率,但是也增加了程序运行时的开销。虚函数表、各个对象中包含的 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

上一篇:【POJ2248】加法链 idfs


下一篇:2021-10-05