C++虚函数表实例分析

栏目: C · 发布时间: 5年前

内容简介:我们先来看看代码:#include <iostream>using namespace std;

我们先来看看代码:

#include <iostream>

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*>(&a);

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中虚函数表结构:

C++虚函数表实例分析

Derive中虚函数表结构:

C++虚函数表实例分析

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

C++虚函数表实例分析

C++虚函数表实例分析

附 GDB调试:

(1) #生成带有调试信息的可执行文件
g++ test.cpp -g -o test    

(2) #载入test
gdb test

(3) #列出Base类代码
(gdb) list Base
1       #include <iostream>
2
3       using namespace std;
4
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 8
Line 8 of "test.cpp" starts at address 0x400af2 <Base::g()> and ends at 0x400afe <Base::g()+12>.
(gdb) info line 9
Line 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      };
11
12      class Derive : public Base{
13      public:
14          void g() {cout<<"derive::g"<<endl;}
15      };

6)#查看Derive函数地址
(gdb) info line 14
Line 14 of "test.cpp" starts at address 0x400b46 <Derive::g()> and ends at 0x400b52 <Derive::g()+12>.

7)#start执行程序,n单步执行
(gdb) start
Temporary breakpoint 1, main () at test.cpp:19
19          cout<<"size of Base: "<<sizeof(Base)<<endl;
(gdb) n
size of Base: 8
22          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

C++虚函数表实例分析

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

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


以上所述就是小编给大家介绍的《C++虚函数表实例分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C算法(第二卷:图算法)(第3版)

C算法(第二卷:图算法)(第3版)

塞德威克(Sedgewick Robert) / 周良忠 / 第1版 (2004年1月1日) / 2004-4 / 38.0

《C算法(第2卷)(图算法)(第3版)(中文版)》所讨论的图算法,都是实际中解决图问题的最重要的已知方法。《C算法(第2卷)(图算法)(第3版)(中文版)》的主要宗旨是让越来越多需要了解这些算法的人的能够掌握这些方法及基本原理。书中根据基本原理从基本住处开始循序渐进地讲解,然后再介绍一些经典方法,最后介绍仍在进行研究和发展的现代技术。精心挑选的实例、详尽的图示以及完整的实现代码与正文中的算法和应用......一起来看看 《C算法(第二卷:图算法)(第3版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换