内容简介:我们先来看看代码:#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++中的多态是用虚函数实现的: 子类覆盖父类的虚函数, 然后声明一个指向子类对象的父类指针, 如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中虚函数表结构:
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) #载入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
Linux公社的RSS地址 : https://www.linuxidc.com/rssFeed.aspx
本文永久更新链接地址: https://www.linuxidc.com/Linux/2019-01/156150.htm
以上所述就是小编给大家介绍的《C++虚函数表实例分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 内核通信之 Netlink 源码分析和实例分析
- 内核通信之 Netlink 源码分析和实例分析
- Redis 实例分析工具
- Golang常量实例分析教程
- 实例分析理解Java字节码
- Java冒泡排序算法实例分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。