c语言实现虚函数表,C++虚函数表实例分析

本文深入探讨了C++中的虚函数表实现机制,通过实例代码展示了如何使用GDB进行验证。文章指出,虚函数表用于实现多态,每个含有虚函数的类都有一个虚函数表,子类会覆盖父类的虚函数。在64位环境中,每个对象开始处有一个_vptr指针指向虚函数表。通过_vptr可以获取虚函数地址并调用相应函数。作者还提供了GDB调试步骤,显示了虚函数表的结构和函数指针的对应关系。
摘要由CSDN通过智能技术生成

我们先来看看代码:

#include

using namespace std;

class Base {

public:

virtual void f() {cout<

virtual void g() {cout<

virtual void h() {cout<

};

class Derive : public Base{

public:

void g() {cout<

};

//可以稍后再看

int main () {

cout<

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;

}

470aee9138cb3e8f3d519abb6f51dc60.png

都知道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(&a);

char *p2 = reinterpret_cast(&a.n);

if(p1 == p2)

{

cout<

}else

{

cout<

}

return 1;

}

(3) 虚函数表

包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.

虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

Base中虚函数表结构:

e7890a626467c7668d8f44ddd67b0fc5.png

Derive中虚函数表结构:

f21d5a5f37abc868aea3c6b4e7176ad2.png

(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)多继承

3b6b4c4f3637e2a013bf5a0a2692bc9c.png

b7177ef7830d04db27db370d71224a1b.png

附 GDB调试:

(1) #生成带有调试信息的可执行文件

g++ test.cpp -g -o test

(2) #载入test

gdb test

(3) #列出Base类代码

(gdb) list Base1 #include

2

3 using namespacestd;4

5 classBase {6 public:7 virtual void f() {cout<

(4) #查看Base函数地址

(gdb) info line7Line7 of "test.cpp" starts at address 0x400ac8 <:f> and ends at 0x400ad4 <:f>.

(gdb) info line8Line8 of "test.cpp" starts at address 0x400af2 <:g> and ends at 0x400afe <:g>.

(gdb) info line9Line9 of "test.cpp" starts at address 0x400b1c <:h> and ends at 0x400b28 <:h>.

(5)#列出Derive代码

(gdb) list Derive7 virtual void f() {cout<

12 class Derive : publicBase{13 public:14 void g() {cout<

(6)#查看Derive函数地址

(gdb) info line14Line14 of "test.cpp" starts at address 0x400b46 <:g> and ends at 0x400b52 <:g>.

(7)#start执行程序,n单步执行

(gdb) start

Temporary breakpoint1, main () at test.cpp:19

19 cout<

(gdb) n

size of Base:8

22Base b;

(gdb)23 Base *d = newDerive();

(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)31f();

(gdb)

(8) #print d对象, 0x400c90为成员变量_vptr的值,也就是函数表的地址

(gdb) p*d

$4 = {_vptr.Base = 0x400c90 }

(gdb) p vptr

$6 = (long *) 0x400c90 (9) #查看函数表值,与之前查看函数地址一致

(gdb) p (long*)vptr[0]

$9 = (long *) 0x400ac8 <:f>(gdb) p (long*)vptr[1]

$10 = (long *) 0x400b46 <:g>(gdb) p (long*)vptr[2]

$11 = (long *) 0x400b1c <:h>

367b181eb47f2cd9c5f5835bda75eeaa.png

0b1331709591d260c1c78e86d0c51c18.png

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值