首页 > 代码库 > C++ 虚函数表解析
C++ 虚函数表解析
首先声明,本文的大部分内容来自大牛文章 http://blog.csdn.net/haoel/article/details/1948051 然后加上自己的一些理解和实验。
系统和编译器: 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 <iostream>using namespace std;#include <stdio.h>#include <stdlib.h>void dump_mem(void* pbeg, size_t size){ char* pbyte = reinterpret_cast<char*>(pbeg); printf("dump bytes begin: 0x%08x, bytes size: %un\n", reinterpret_cast<unsigned long int>(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<unsigned long>(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" <<hex<<(long) pVtab <<endl; cout <<"pVtable content:\t" <<hex <<(long) *pVtab <<endl; cout <<"pVtable+1 content:\t" <<hex <<(long) *(pVtab+1) <<endl; cout <<"pVtable+2 content:\t" <<hex <<(long) *(pVtab+2) <<endl; cout <<"pVtable[0] content:\t" <<hex <<(long) pVtab[0] <<endl; cout <<"pVtable[1] content:\t" <<hex <<(long) pVtab[1] <<endl; cout <<"pVtable[2] content:\t" <<hex <<(long) pVtab[2] <<endl; cout <<"pVtable[0][0] content:\t" <<hex << (long) pVtab[0][0]<<endl; cout <<"pVtable[0][1] content:\t" <<hex << (long) pVtab[0][1]<<endl; cout <<"pVtable[0][2] content:\t" <<hex << (long) pVtab[0][2]<<endl; cout <<"pVtable[1][0] content:\t" <<hex << (long) pVtab[1][0]<<endl; cout <<"pVtable[1][1] content:\t" <<hex << (long) pVtab[1][1]<<endl; cout <<"pVtable[1][2] content:\t" <<hex << (long) pVtab[1][2]<<endl; cout <<"pVtable[2][0] content:\t" <<hex << (long) pVtab[2][0]<<endl; cout <<"pVtable[2][1] content:\t" <<hex << (long) pVtab[2][1]<<endl; cout <<"pVtable[2][2] content:\t" <<hex << (long) pVtab[2][2]<<endl; //Base1‘s vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0); pFun = (Fun)pVtab[0][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1); pFun = (Fun)pVtab[0][1]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2); pFun = (Fun)pVtab[0][2]; pFun(); //Derive‘s vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3); pFun = (Fun)pVtab[0][3]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[0][4]; cout<<pFun<<endl; //Base2‘s vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Fun)pVtab[1][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Fun)pVtab[1][1]; pFun(); pFun = (Fun)pVtab[1][2]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[1][3]; cout<<pFun<<endl; //Base3‘s vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Fun)pVtab[2][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Fun)pVtab[2][1]; pFun(); pFun = (Fun)pVtab[2][2]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[2][3]; cout<<pFun<<endl; dump_mem((void*)pVtab[0], 100); dump_mem((void*)pVtab[1], 100); dump_mem((void*)pVtab[2], 100); pFun = (Fun)pVtab[0][0]; pFun(); pFun = (Fun)pVtab[1][0]; pFun(); pFun = (Fun)pVtab[2][0]; pFun(); //pFun = (Fun)0x401604; //pFun(); //pFun = (Fun)0X401556; //pFun(); return 0;}
编译运行 获得输出:
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<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))@@GLIBCXX_3.4 U std::basic_ostream<char, std::char_traits<char> >::operator<<(std::ios_base& (*)(std::ios_base&))@@GLIBCXX_3.4 U std::basic_ostream<char, std::char_traits<char> >::operator<<(bool)@@GLIBCXX_3.4 U std::basic_ostream<char, std::char_traits<char> >::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<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@@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<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, 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();
却都调用了 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()函数的地址, 然后把他付给函数指针,即可调用函数。
C++ 虚函数表解析