C++虚函数表分析
在<<反C++>>文中提到的 VC6.0 上的编译器支持一个d1reportAllClassLayout的开关, 能够输出全部对象的内存布局信息, 我自己经常使用vs2005来开发,所以这里我就以vs2005为例讲怎么设置d1reportAllClassLayout。
右键项目属性(Properties)—》配制属性(Configuration Properties)—》C/C++--》命令行(Command Line)的框里输入/d1reportAllClassLayout,就可以看类的对象布局。例如以下图:(当然也能够使用命令行:cl –d1reportSingleClassLayout[classname] test.cpp)
測试代码:
class CBase
{
public:
virtual void fun(void) {} private:
int m_valuable;
};
输出结果:
class CBase size(8):
+---
0 | {vfptr}
4 | m_valuable
+---
CBase::$vftable@:
| &CBase_meta
| 0
0 | &CBase::fun
CBase::fun this adjustor: 0
CDerived:非virtual继承CBase,持有一个virtual重写方法和一个virtual新方法。一个成员变量
class CDerived: public CBase
{
public:
void fun(void) { }
virtual void vfun(void) { } public:
int m_derived;
};
编译后输出结果:
class CDerived size(12):
+---
| +--- (base class CBase)
0 | | {vfptr}
4 | | m_valuable
| +---
8 | m_derived
+---
CDerived::$vftable@:
| &CDerived_meta
| 0
0 | &CDerived::fun
1 | &CDerived::vfun
CDerived::fun this adjustor: 0
CDerived::vfun this adjustor: 0
当中vftable@CBase的CBase::fun项在这里更新为CDerived::fun,同一时候添加了一项CDerived::vfun。
CDerived2:virtual继承CBase,持有一个virtual重写方法和一个virtual新方法,一个成员变量。因此结构为vftable@自身,vbtable@自身,member@自身,以及CBase结构。
总结:
class CDerived2: virtual public CBase
{
public:
void fun(void) { }
virtual void vfun(void) { } public:
int m_derived;
};
结果输出:
class CDerived2 size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | m_derived
+---
+--- (virtual base CBase)
12 | {vfptr}
16 | m_valuable
+---
CDerived2::$vftable@CDerived2@:
| &CDerived2_meta
| 0
0 | &CDerived2::vfun
CDerived2::$vbtable@:
0 | -4
1 | 8 (CDerived2d(CDerived2+4)CBase)
CDerived2::$vftable@CBase@:
| -12
0 | &CDerived2::fun
CDerived2::fun this adjustor: 12
CDerived2::vfun this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase 12 4 4 0
当中vftable@自身仅仅有一项:CDerived2::vfun(),vbtable@自身仅仅有一项:它virtual继承的父类CBase。而vftable@CBase原来的CBase::fun更新为CDerived2::fun。 CDerived3:virtual继承CBase,因此结构为vbtable@自身,member@自身。CBase结构
class CDerived3: virtual public CBase
{
public:
void fun(void) { } public:
int m_derived3;
};
结果输出:
class CDerived3 size(16):
+---
0 | {vbptr}
4 | m_derived3
+---
+--- (virtual base CBase)
8 | {vfptr}
12 | m_valuable
+---
CDerived3::$vbtable@:
0 | 0
1 | 8 (CDerived3d(CDerived3+0)CBase)
CDerived3::$vftable@:
| -8
0 | &CDerived3::fun
CDerived3::fun this adjustor: 8
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase 8 0 4 0
CGDerived:继承CDerived2、CDerived3
class CGDerived: public CDerived2, public CDerived3
{
public:
void vfun() { }
virtual void vgfun() { } public:
int m_gd;
};
输出:
class CGDerived size(32):
+---
| +--- (base class CDerived2)
0 | | {vfptr}
4 | | {vbptr}
8 | | m_derived
| +---
| +--- (base class CDerived3)
12 | | {vbptr}
16 | | m_derived3
| +---
20 | m_gd
+---
+--- (virtual base CBase)
24 | {vfptr}
28 | m_valuable
+---
CGDerived::$vftable@CDerived2@:
| &CGDerived_meta
| 0
0 | &CGDerived::vfun
1 | &CGDerived::vgfun
CGDerived::$vbtable@CDerived2@:
0 | -4
1 | 20 (CGDerivedd(CDerived2+4)CBase)
CGDerived::$vbtable@CDerived3@:
0 | 0
1 | 12 (CGDerivedd(CDerived3+0)CBase)
CGDerived::$vftable@CBase@:
| -24
0 | &thunk: this-=12; goto CDerived2::fun
CGDerived::vfun this adjustor: 0
CGDerived::vgfun this adjustor: 0
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase 24 4 4 0
因此首先是CDerived2的结构和CDerived3的结构,自己的新virtual方法vgfun则加入在最左父类CDerived2的虚函数表中。然后是自己的成员。 最后。CDerived2和CDerived3的父类CBase结构也带入当中。 它的fun默认指向CGDerived的最左父类CDerived2::fun。 CGG:继承CGDerived
class CGG: public CGDerived
{
public:
int m_kc;
};
输出:
class CGG size(36):
+---
| +--- (base class CGDerived)
| | +--- (base class CDerived2)
0 | | | {vfptr}
4 | | | {vbptr}
8 | | | m_derived
| | +---
| | +--- (base class CDerived3)
12 | | | {vbptr}
16 | | | m_derived3
| | +---
20 | | m_gd
| +---
24 | m_kc
+---
+--- (virtual base CBase)
28 | {vfptr}
32 | m_valuable
+---
CGG::$vftable@CDerived2@:
| &CGG_meta
| 0
0 | &CGDerived::vfun
1 | &CGDerived::vgfun
CGG::$vbtable@CDerived2@:
0 | -4
1 | 24 (CGGd(CDerived2+4)CBase)
CGG::$vbtable@CDerived3@:
0 | 0
1 | 16 (CGGd(CDerived3+0)CBase)
CGG::$vftable@CBase@:
| -28
0 | &thunk: this-=16; goto CDerived2::fun
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase 28 4 4 0
添加一个变量,基本仅仅是把CGDerived的结构再套一层。最后加上自己的成员变量。 vbtable所指向的父类结构依旧在最后。 空类
class CBase2
{ };
输出:
class CBase2 size(1):
+---
+---
CD2:virtual继承则一定会创建vbtable,用vbptr指针指向,因此size为4
class CD2: virtual public CBase2
{ };
输出:
class CD2 size(4):
+---
0 | {vbptr}
+---
+--- (virtual base CBase2)
+---
CD2::$vbtable@:
0 | 0
1 | 4 (CD2d(CD2+0)CBase2)
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase2 4 0 4 0 CE:非virtual继承CD2和CDerived2。于是依照类的声明顺序,先带入CDerived2的结构,再带入CD2的结构(而不是按继承顺序)
class CE: public CD2, public CDerived2
{ };
输出:
class CE size(24):
+---
| +--- (base class CDerived2)
0 | | {vfptr}
4 | | {vbptr}
8 | | m_derived
| +---
| +--- (base class CD2)
12 | | {vbptr}
| +---
+---
+--- (virtual base CBase2)
+---
+--- (virtual base CBase)
16 | {vfptr}
20 | m_valuable
+---
CE::$vftable@CDerived2@:
| &CE_meta
| 0
0 | &CDerived2::vfun
CE::$vbtable@CD2@:
0 | 0
1 | 4 (CEd(CD2+0)CBase2)
2 | 4 (CEd(CE+12)CBase)
CE::$vbtable@CDerived2@:
0 | -4
1 | 12 (CEd(CDerived2+4)CBase)
CE::$vftable@CBase@:
| -16
0 | &thunk: this-=4; goto CDerived2::fun
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase2 16 12 4 0
CBase 16 12 8 0
CF:virtual继承CBase和CBase2,vbtable此时的项便有两个。此时依照继承的顺序,而不是依照类的声明顺序
class CF: virtual public CBase2, virtual public CBase
{ };
输出:
class CF size(12):
+---
0 | {vbptr}
+---
+--- (virtual base CBase2)
+---
+--- (virtual base CBase)
4 | {vfptr}
8 | m_valuable
+---
CF::$vbtable@:
0 | 0
1 | 4 (CFd(CF+0)CBase2)
2 | 4 (CFd(CF+0)CBase)
CF::$vftable@:
| -4
0 | &CBase::fun
vbi: class offset o.vbptr o.vbte fVtorDisp
CBase2 4 0 4 0
CBase 4 0 8 0
总结:
继承方式:非virtual继承:导入各个父类的结构(依照父类声明的顺序,从上到下),自身member在最后
重写virtual方法:更新该方法最早定义的类的vftable
新的virtual方法:在最左父类的vftable添加
继承方式:有virtual继承:在自身member后添加virtual父类的结构(依照子类继承的顺序从左到右)。同一时候在最前面添加vbtable(假设没有的话),添加一项指向父类结构
重写virtual方法:更新该方法的最早定义的类的vftable
新的virtual方法:在自身最前面添加vftable(假设没有的话),在自己的vftable添加
附 全部源代码:
#ifndef TEST_CBASE_H
#define TEST_CBASE_H class CBase
{
public:
CBase();
explicit CBase(int valuabel);
~CBase(); virtual void fun(void) {} private:
int m_valuable;
}; class CDerived: public CBase
{
public:
void fun(void) { }
virtual void vfun(void) { } public:
int m_derived;
}; class CDerived2: virtual public CBase
{
public:
void fun(void) { }
virtual void vfun(void) { } public:
int m_derived;
}; class CDerived3: virtual public CBase
{
public:
void fun(void) { } public:
int m_derived3;
}; class CGDerived: public CDerived2, public CDerived3
{
public:
void vfun() { }
virtual void vgfun() { } public:
int m_gd;
}; class CGG: public CGDerived
{
public:
int m_kc;
}; class CBase2
{ }; class CD2: virtual public CBase2
{ }; class CE: public CD2, public CDerived2
{ }; class CF: virtual public CBase2, virtual public CBase
{ }; #endif//TEST_CBASE_H
版权声明:本文博客原创文章,博客,未经同意,不得转载。
C++虚函数表分析的更多相关文章
- C++单继承、多继承情况下的虚函数表分析
C++的三大特性之一的多态是基于虚函数实现的,而大部分编译器是采用虚函数表来实现虚函数,虚函数表(VTAB)存在于可执行文件的只读数据段中,指向VTAB的虚表指针(VPTR)是包含在类的每一个实例当中 ...
- 类虚函数表原理实现分析(当我们将虚表地址[n]中的函数替换,那么虚函数的实现就由我们来控制了)
原理分析 当调用一个虚函数时, 编译器生成的代码会调用 虚表地址[0](param1, param2)这样的函数. 已经不是在调用函数名了. 当我们将虚表地址[n]中的函数实现改为另外的函数, 虚函数 ...
- C++ 多态、虚函数机制以及虚函数表
1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...
- C++ Daily 《5》----虚函数表的共享问题
问题: 包含一个以上虚函数的 class B, 它所定义的 对象是否共用一个虚函数表? 分析: 由于含有虚函数,因此对象内存包含了一个指向虚函数表的指针,但是这个指针指向的是同一个虚函数表吗? 实验如 ...
- 深入理解C++虚函数表
虚函数表是C++类中存放虚函数的一张表,理解虚函数表对于理解多态很重要. 本次使用的编译器是VS2013,为了简化操作,不用去操作函数指针,我使用到了VS的CL编译选项来查看类的内存布局. CL使用方 ...
- 从逆向的角度去理解C++虚函数表
很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中 ...
- C++对象内存模型2 (虚函数,虚指针,虚函数表)
从例子入手,考察如下带有虚函数的类的对象内存模型: class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1 ...
- 深入剖析C++多态、VPTR指针、虚函数表
在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...
- 详谈C++虚函数表那回事(多重继承关系)
上一篇说了一般继承,也就是单继承的虚函数表,接下来说说多重继承的虚函数表: 1.无虚函数覆盖的多重继承: 代码: #pragma once //无覆盖,多重继承 class Base1 { publi ...
随机推荐
- Codeforces Round#201(div1) D. Lucky Common Subsequence
题意:给定两个串,求出两个串的最长公共子序列,要求该公共子序列不包含virus串. 用dp+kmp实现 dp[i][j][k]表示以i结尾的字符串和以j结尾的字符串的公共子序列的长度(其中k表示该公共 ...
- QTP小应用一则
昨天收到一个任务,将270多个视频文件按照统一的编码要求,转换为其他格式,而且给的转换器居然不能批量转换! 在痛苦地转换了30多个之后,我才想起来使用QTP的数据驱动测试方法可以解决这个问题! 于是我 ...
- .NET 使用 MySql.Data.dll 动态库操作MySql的帮助类--MySqlHelper
.NET 使用 MySql.Data.dll 动态库操作MySql的帮助类--MySqlHelper 參考演示样例代码,例如以下所看到的: /// <summary> /// MySql ...
- tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)
tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳 以上的各种应用都是在此拓展而来的. 割点:如果一个图去掉 ...
- erlang如何有效地监视大量的并发连接
阅读erlang一些开源web框架RabbitMQ.Ranch,他们使用多个进程在同一时间accept一socket.以这样的方式,使socketport监控共享很多其他的机会调度工作,但,在erla ...
- Portlet MVC框架
Portlet MVC框架 16.1. 介绍 Spring不仅支持传统(基于Servlet)的Web开发,也支持JSR-168 Portlet开发. Portlet MVC框架尽可能多地采用Web ...
- JavaScript 常见陷阱
JavaScript中的一些特性和通常我们想象的不太一样.这里我总结了一些有悖直觉的语言特性. 1 数组 1.1 数组的遍历 在直接支持for a in b的语言中,比方Python/Ruby里的a的 ...
- Android 通过系统使用NotificationListenerService 监听各种Notification的用法
NotificationListenerService是通过系统调起的服务,当有应用发起通知的时候,系统会将通知的动作和信息回调给NotificationListenerService. 在继承Not ...
- 配置jndi服务,javax.naming.NamingException的四种情况
1.当jndi服务没有启动,或者jndi服务的属性没有设置正确,抛出如下异常: javax.naming.CommunicationException: Can't find SerialContex ...
- ibatis实战之OR映射
相对Hibernate等ORM实现而言,ibatis的映射配置更为简洁直接,以下是一个典型的配置文件. <?xml version="1.0" encoding=" ...