手机版
你好,游客 登录 注册
背景:
阅读新闻

C++虚函数表实例分析

[日期:2019-01-03 18:04:03] 来源:linux公社   作者:linux公社 [字体: ]

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: void g() {cout "derive::g" endl;}};

//可以稍后再看int main () { cout "size of Base: " sizeof(Base) endl;

typedef void(*Func)(void); Base b; Base *d = new Derive();

long* pvptr = (long*)d; long* vptr = (long*)*pvptr; Func f = (Func)vptr[0]; Func g = (Func)vptr[1]; Func h = (Func)vptr[2];

f(); g(); h();

return 0;}

C++虚函数表实例分析

都知道C++中的多态是用虚函数实现的: 子类覆盖父类的虚函数, 然后声明一个指向子类对象的父类指针, 如Base *b = new Derive();

当调用b- f()时, 调用的是子类的Derive::f()。

这种机制内部由虚函数表实现,下面对虚函数表结构进行分析,并且用GDB验证。

1. 基础知识:

(1) 32位os 指针长度为4字节, 64位os 指针长度为8字节, 下面的分析环境为64位 linux g++ 4.8.4.

(2) new一个对象时, 只为类中成员变量分配空间, 对象之间共享成员函数。

2. _vptr

运行下上面的代码发现sizeof(Base) = 8, 说明编译器在类中自动添加了一个8字节的成员变量, 这个变量就是_vptr, 指向虚函数表的指针。

_vptr有些文章里说gcc是把它放在对象内存的末尾,VC是放在开始, 我编译是用的g++,验证了下是放在开始的:

验证代码:取对象a的地址与a第一个成员变量n的地址比较,如果不等,说明对象地址开始放的是_vptr. 也可以用gdb直接print a 会发现_vptr在开始

class A{public: int n; virtual void Foo(void){}};

int main(){ A a; char *p1 = reinterpret_cast char* ( char *p2 = reinterpret_cast char* ( a.n); if(p1 == p2) { cout "vPtr is in the end of class instance!" endl; }else { cout "vPtr is in the head of class instance!" endl; } return 1;}

(3) 虚函数表 包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr. 虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

Base中虚函数表结构:

Derive中虚函数表结构:

(4)验证运行上面代码结果: size of Base: 8 base::f derive::g base::h

说明Derive的虚函数表结构跟上面分析的是一致的: d对象的首地址就是vptr指针的地址-pvptr, 取pvptr的值就是vptr-虚函数表的地址 取vptr中[0][1][2]的值就是这三个函数的地址 通过函数地址就直接可以运行三个虚函数了。 函数表中Base::g()函数指针被Derive中的Derive::g()函数指针覆盖, 所以执行的时候是调用的Derive::g()

(5)多继承

附 GDB调试:(1) #生成带有调试信息的可执行文件g++ test.cpp -g -o test (2) #载入testgdb test(3) #列出Base类代码(gdb) list Base1 #include iostream 3 using namespace std;5 class Base {6 public:7 virtual void f() {cout "base::f" endl;}8 virtual void g() {cout "base::g" endl;}9 virtual void h() {cout "base::h" endl;}10 };(4) #查看Base函数地址(gdb) info line 7 Line 7 of "test.cpp" starts at address 0x400ac8 Base::f() and ends at 0x400ad4 Base::f()+12 .(gdb) info line 8Line 8 of "test.cpp" starts at address 0x400af2 Base::g() and ends at 0x400afe Base::g()+12 .(gdb) info line 9Line 9 of "test.cpp" starts at address 0x400b1c Base::h() and ends at 0x400b28 Base::h()+12 .(5)#列出Derive代码(gdb) list Derive 7 virtual void f() {cout "base::f" endl;}8 virtual void g() {cout "base::g" endl;}9 virtual void h() {cout "base::h" endl;}10 };12 class Derive : public Base{13 public:14 void g() {cout "derive::g" endl;}15 };(6)#查看Derive函数地址(gdb) info line 14Line 14 of "test.cpp" starts at address 0x400b46 Derive::g() and ends at 0x400b52 Derive::g()+12 .(7)#start执行程序,n单步执行(gdb) startTemporary breakpoint 1, main () at test.cpp:1919 cout "size of Base: " sizeof(Base) endl;(gdb) nsize of Base: 822 Base b;(gdb)23 Base *d = new Derive();(gdb)25 long* pvptr = (long*)d;(gdb)26 long* vptr = (long*)*pvptr;(gdb)27 Func f = (Func)vptr[0];(gdb)28 Func g = (Func)vptr[1];(gdb)29 Func h = (Func)vptr[2];(gdb)31 f();(gdb)(8) #print d对象, 0x400c90为成员变量_vptr的值,也就是函数表的地址(gdb) p *d $4 = {_vptr.Base = 0x400c90 vtable for Derive+16 }(gdb) p vptr$6 = (long *) 0x400c90 vtable for Derive+16 (9) #查看函数表值,与之前查看函数地址一致(gdb) p (long*)vptr[0]$9 = (long *) 0x400ac8 Base::f() (gdb) p (long*)vptr[1]$10 = (long *) 0x400b46 Derive::g() (gdb) p (long*)vptr[2]$11 = (long *) 0x400b1c Base::h() 

另vptr. vtable内存位置, refer https://www.linuxidc.com/Linux/2019-01/156151.htm

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

本文永久更新链接地址:https://www.linuxidc.com/Linux/2019-01/156150.htm

linux Ubunutu下使用g++ 编译错误之没有那个文件或目录 C++虚函数在g++中的实现分析

Linux公社的RSS地址http://www.it56.cn/rss.xml

本文永久更新链接地址www.it56.cn/RedLinux/8489.html

linux