博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++ 虚函数表解析
阅读量:4344 次
发布时间:2019-06-07

本文共 11839 字,大约阅读时间需要 39 分钟。

首先声明,本文的大部分内容来自大牛文章  然后加上自己的一些理解和实验。

系统和编译器: ubuntu 14.04 64bits + g++4.8.2

虚函数表

 

C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

 

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

 

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

 

假设我们有这样的一个类:

 

class Base {     public:            virtual void f() { cout << "Base::f" << endl; }            virtual void g() { cout << "Base::g" << endl; }            virtual void h() { cout << "Base::h" << endl; } };

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

typedef void(*Fun)(void);            Base b;            Fun pFun = NULL;             cout << "虚函数表地址:" << (int*)(&b) << endl;            cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;             // Invoke the first virtual function             pFun = (Fun)*((int*)*(int*)(&b));            pFun();

实际运行经果如下:(Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)

虚函数表地址:0012FED4

虚函数表 — 第一个函数地址:0044F148

Base::f

 

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

 

            (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

 

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

 

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

这种情况是考虑到多重继承的时候考虑的,参考

 

 

 

  

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

 

一般继承(无虚函数覆盖)

 

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

 

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

 对于实例:Derive d; 的虚函数表如下:

  

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

 

 

一般继承(有虚函数覆盖)

 

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

 

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

 

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

 

这样,我们就可以看到对于下面这样的程序,

 

            Base *b = new Derive();

 

            b->f();

 

b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

 

多重继承(无虚函数覆盖)

 

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

  

对于子类实例中的虚函数表,是下面这个样子:

 

 

 我们可以看到:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

 这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

  

多重继承(有虚函数覆盖)

 

下面我们再来看看,如果发生虚函数覆盖的情况。

 

下图中,我们在子类中覆盖了父类的f()函数。

  

 

 

 下面是对于子类实例中的虚函数表的图:

 

  

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

 

Derive d;            Base1 *b1 = &d;            Base2 *b2 = &d;            Base3 *b3 = &d;            b1->f(); //Derive::f()            b2->f(); //Derive::f()            b3->f(); //Derive::f()             b1->g(); //Base1::g()            b2->g(); //Base2::g()            b3->g(); //Base3::g()

 

 

以上内容基本为摘抄

下面的这个例子也为原文的例子,由于我的系统是64位的,所以需要将原来的int(32bits)换成long 64bits。另外,介绍下我的调试、测试工具:

1 加入了dump_mem函数,来打印相关的内存输出,从而使内存的内容更加明了。

2 使用 nm a.out | c++filt 来查看可执行文件中的所有符号,从而和对应的函数调用关系对应

#include 
using namespace std;#include
#include
void dump_mem(void* pbeg, size_t size){ char* pbyte = reinterpret_cast
(pbeg); printf("dump bytes begin: 0x%08x, bytes size: %un\n", reinterpret_cast
(pbeg), size); if (size == 0) return; puts("offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); puts("-----------------------------------------------------------"); int col = 0; for (size_t i = 0; true; i++) { if (col == 0) printf("0x%08X: ", reinterpret_cast
(pbyte + i)); printf("%02X", pbyte[i]& 0xff); if (i == size - 1) break; if (col == 15) { putchar('\n'); col = 0; } else { putchar(' '); col++; } } putchar('\n');}class Base1 { public: virtual void f() { cout << "Base1::f" << endl; } virtual void g() { cout << "Base1::g" << endl; } virtual void h() { cout << "Base1::h" << endl; }};class Base2 { public: virtual void f() { cout << "Base2::f" << endl; } virtual void g() { cout << "Base2::g" << endl; } virtual void h() { cout << "Base2::h" << endl; }};class Base3 { public: virtual void f() { cout << "Base3::f" << endl; } virtual void g() { cout << "Base3::g" << endl; } virtual void h() { cout << "Base3::h" << endl; }};class Derive : public Base1, public Base2, public Base3 { public: virtual void f() { cout << "Derive::f" << endl; } virtual void g1() { cout << "Derive::g1" << endl; }};typedef void(*Fun)(void);int main(){ Fun pFun = NULL; Base1 b1; Base2 b2; Base3 b3; Derive d; long ** pVtab = (long **)&d; cout <<"pVtable address:\t" <
<<(long) pVtab <

 

编译运行 获得输出:

pVtable address:        7fffeafe67d0pVtable content:        4019b0pVtable+1 content:      4019e0pVtable+2 content:      401a08pVtable[0] content:     4019b0pVtable[1] content:     4019e0pVtable[2] content:     401a08pVtable[0][0] content:  401574pVtable[0][1] content:  401424pVtable[0][2] content:  40144epVtable[1][0] content:  40159epVtable[1][1] content:  4014a2pVtable[1][2] content:  4014ccpVtable[2][0] content:  4015a4pVtable[2][1] content:  401520pVtable[2][2] content:  40154aDerive::fBase1::gBase1::hDerive::g11Derive::fBase2::gBase2::h1Derive::fBase3::gBase3::h0dump bytes begin: 0x004019b0, bytes size: 100noffset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F-----------------------------------------------------------0x004019B0: 74 15 40 00 00 00 00 00 24 14 40 00 00 00 00 000x004019C0: 4E 14 40 00 00 00 00 00 AA 15 40 00 00 00 00 000x004019D0: F8 FF FF FF FF FF FF FF E0 1A 40 00 00 00 00 000x004019E0: 9E 15 40 00 00 00 00 00 A2 14 40 00 00 00 00 000x004019F0: CC 14 40 00 00 00 00 00 F0 FF FF FF FF FF FF FF0x00401A00: E0 1A 40 00 00 00 00 00 A4 15 40 00 00 00 00 000x00401A10: 20 15 40 00dump bytes begin: 0x004019e0, bytes size: 100noffset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F-----------------------------------------------------------0x004019E0: 9E 15 40 00 00 00 00 00 A2 14 40 00 00 00 00 000x004019F0: CC 14 40 00 00 00 00 00 F0 FF FF FF FF FF FF FF0x00401A00: E0 1A 40 00 00 00 00 00 A4 15 40 00 00 00 00 000x00401A10: 20 15 40 00 00 00 00 00 4A 15 40 00 00 00 00 000x00401A20: 00 00 00 00 00 00 00 00 30 1B 40 00 00 00 00 000x00401A30: F6 14 40 00 00 00 00 00 20 15 40 00 00 00 00 000x00401A40: 4A 15 40 00dump bytes begin: 0x00401a08, bytes size: 100noffset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F-----------------------------------------------------------0x00401A08: A4 15 40 00 00 00 00 00 20 15 40 00 00 00 00 000x00401A18: 4A 15 40 00 00 00 00 00 00 00 00 00 00 00 00 000x00401A28: 30 1B 40 00 00 00 00 00 F6 14 40 00 00 00 00 000x00401A38: 20 15 40 00 00 00 00 00 4A 15 40 00 00 00 00 000x00401A48: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x00401A58: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x00401A68: 50 1B 40 00Derive::fDerive::fDerive::f

 

使用 nm dump 所有符号表

ego@ubuntu:~/myProg/cpp$ nm a.out | c++filt 0000000000602e18 d _DYNAMIC0000000000603000 d _GLOBAL_OFFSET_TABLE_00000000004012ce t _GLOBAL__sub_I__Z8dump_memPvm0000000000401700 R _IO_stdin_used                 w _ITM_deregisterTMCloneTable                 w _ITM_registerTMCloneTable                 w _Jv_RegisterClasses0000000000401291 t __static_initialization_and_destruction_0(int, int)0000000000400afd T dump_mem(void*, unsigned long)00000000004013fa W Base1::f()0000000000401424 W Base1::g()000000000040144e W Base1::h()00000000004015d4 W Base1::Base1()00000000004015d4 W Base1::Base1()0000000000401478 W Base2::f()00000000004014a2 W Base2::g()00000000004014cc W Base2::h()00000000004015ea W Base2::Base2()00000000004015ea W Base2::Base2()00000000004014f6 W Base3::f()0000000000401520 W Base3::g()000000000040154a W Base3::h()0000000000401600 W Base3::Base3()0000000000401600 W Base3::Base3()0000000000401574 W Derive::f()00000000004015aa W Derive::g1()0000000000401616 W Derive::Derive()0000000000401616 W Derive::Derive()                 U std::basic_ostream
>::operator<<(std::basic_ostream
>& (*)(std::basic_ostream
>&))@@GLIBCXX_3.4 U std::basic_ostream
>::operator<<(std::ios_base& (*)(std::ios_base&))@@GLIBCXX_3.4 U std::basic_ostream
>::operator<<(bool)@@GLIBCXX_3.4 U std::basic_ostream
>::operator<<(long)@@GLIBCXX_3.4 U std::ios_base::Init::Init()@@GLIBCXX_3.4 U std::ios_base::Init::~Init()@@GLIBCXX_3.40000000000401374 W std::ios_base::setf(std::_Ios_Fmtflags, std::_Ios_Fmtflags)00000000004013d2 W std::hex(std::ios_base&)0000000000603100 B std::cout@@GLIBCXX_3.4 U std::basic_ostream
>& std::endl
>(std::basic_ostream
>&)@@GLIBCXX_3.40000000000603279 b std::__ioinit0000000000401346 W std::operator&=(std::_Ios_Fmtflags&, std::_Ios_Fmtflags)00000000004012e3 W std::operator&(std::_Ios_Fmtflags, std::_Ios_Fmtflags)000000000040130b W std::operator~(std::_Ios_Fmtflags) U std::basic_ostream
>& std::operator<<
>(std::basic_ostream
>&, char const*)@@GLIBCXX_3.40000000000401319 W std::operator|=(std::_Ios_Fmtflags&, std::_Ios_Fmtflags)00000000004012f7 W std::operator|(std::_Ios_Fmtflags, std::_Ios_Fmtflags)0000000000401b70 V typeinfo for Base10000000000401b50 V typeinfo for Base20000000000401b30 V typeinfo for Base30000000000401ae0 V typeinfo for Derive0000000000401b60 V typeinfo name for Base10000000000401b40 V typeinfo name for Base20000000000401b28 V typeinfo name for Base30000000000401ac8 V typeinfo name for Derive0000000000401aa0 V vtable for Base10000000000401a60 V vtable for Base20000000000401a20 V vtable for Base300000000004019a0 V vtable for Derive00000000006030a0 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.30000000000603220 V vtable for __cxxabiv1::__vmi_class_type_info@@CXXABI_1.300000000004015a4 W non-virtual thunk to Derive::f()000000000040159e W non-virtual thunk to Derive::f()00000000004020d8 r __FRAME_END__0000000000602e10 d __JCR_END__0000000000602e10 d __JCR_LIST__0000000000603098 D __TMC_END__0000000000603098 B __bss_start U __cxa_atexit@@GLIBC_2.2.50000000000603088 D __data_start0000000000400ab0 t __do_global_dtors_aux0000000000602e08 t __do_global_dtors_aux_fini_array_entry0000000000603090 D __dso_handle0000000000602df8 t __frame_dummy_init_array_entry w __gmon_start__0000000000602e08 t __init_array_end0000000000602df8 t __init_array_start00000000004016f0 T __libc_csu_fini0000000000401680 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.50000000000603098 D _edata0000000000603280 B _end00000000004016f4 T _fini00000000004008f8 T _init0000000000400a10 T _start0000000000603278 b completed.69720000000000603088 W data_start0000000000400a40 t deregister_tm_clones0000000000400ad0 t frame_dummy0000000000400bf4 T main U printf@@GLIBC_2.2.5 U putchar@@GLIBC_2.2.5 U puts@@GLIBC_2.2.50000000000400a70 t register_tm_clones

另外,注意,在上面的例子中,

  pFun = (Fun)pVtab[0][0];    pFun();    pFun = (Fun)pVtab[1][0];    pFun();    pFun = (Fun)pVtab[2][0];    pFun();
pVtable[0][0] content:  401574pVtable[0][1] content:  401424pVtable[0][2] content:  40144epVtable[1][0] content:  40159epVtable[1][1] content: 4014a2 pVtable[1][2] content: 4014cc pVtable[2][0] content: 4015a4 pVtable[2][1] content: 401520 pVtable[2][2] content: 40154a
pVtable[0][0]和pVtable[1][0]和pVtable[2][0]的值不相同

却都调用了 Derive::f,这说明内部调用时有调整。

 

 

另外,记录下有关 long ** pVtab = (long **)&d; 的使用。

 

pVtab 是一个指向指针的指针,  

p[0] 等同于 *p, 还是一个指针,指向第一个虚函数表

p[1] 等同于 *(p+1), 还是一个指针,指向第二个虚函数表

p[2] 等同于 *(p+2), 还是一个指针,指向第三个虚函数表

p[0][0] 是一个long int,我们这里,是**p

p[1][2] 是一个long int,我们这里,是*(*(p_1) +2),就是base2::g()函数的地址, 然后把他付给函数指针,即可调用函数。

转载于:https://www.cnblogs.com/diegodu/p/4000653.html

你可能感兴趣的文章
spring第二冲刺阶段第七天
查看>>
搜索框键盘抬起事件2
查看>>
阿里百川SDK初始化失败 错误码是203
查看>>
透析Java本质-谁创建了对象,this是什么
查看>>
BFS和DFS的java实现
查看>>
关于jquery中prev()和next()的用法
查看>>
一、 kettle开发、上线常见问题以及防错规范步骤
查看>>
eclipse没有server选项
查看>>
CRC码计算及校验原理的最通俗诠释
查看>>
使用Gitbook来编写你的Api文档
查看>>
jquery扩展 $.fn
查看>>
Markdown指南
查看>>
influxDB的安装和简单使用
查看>>
JPA框架学习
查看>>
JPA、JTA、XA相关索引
查看>>
机器分配
查看>>
php opcode缓存
查看>>
springcloud之Feign、ribbon设置超时时间和重试机制的总结
查看>>
观看杨老师(杨旭)Asp.Net Core MVC入门教程记录
查看>>
UIDynamic(物理仿真)
查看>>