哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout 。看个复杂的例子吧(如下),现在假设我们想知道Derived类的对象布局,怎么办? 在Project Properties->C++->Command Line->Additional Options里面加上/d1reportSingleClassLayoutDerived吧!

class CommonBase
{
int co;
};

class Base1: virtual public CommonBase
{
public:
virtual void print1() {}
virtual void print2() {}
private:
int b1;
};

class Base2: virtual public CommonBase
{
public:
virtual void dump1() {}
virtual void dump2() {}
private:
int b2;
};

class Derived: public Base1, public Base2
{
public:
void print2() {}
void dump2() {}
private:
int d;
};

int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}

F5编译之,你会惊奇地发现,Output里面有如下字样:

1 class Derived size(32):
2 +---
3 | +--- (base class Base1)
4 0 | | {vfptr}
5 4 | | {vbptr}
6 8 | | b1
7 | +---
8 | +--- (base class Base2)
9 12 | | {vfptr}
10 16 | | {vbptr}
11 20 | | b2
12 | +---
13 24 | d
14 +---
15 +--- (virtual base CommonBase)
16 28 | co
17 +---
18
19 Derived::$vftable@Base1@:
20 0 | &Base1::print1
21 1 | &Derived::print2
22
23 Derived::$vftable@Base2@:
24 0 | &Base2::dump1
25 1 | &Derived::dump2
26
27 Derived::$vbtable@Base1@:
28 0 | -4
29 1 | 24 (Derivedd(Base1+4)CommonBase)
30
31 Derived::$vbtable@Base2@:
32 0 | -4
33 1 | 12 (Derivedd(Base2+4)CommonBase)
34
35 Derived::print2 this adjustor: 0
36 Derived::dump2 this adjustor: 12

看到了吗? VC8居然输出了Derived对象的完整布局! 我们终于可以不必两眼一抹黑般的去peek/poke了....第1行表明,Derived对象总占用了32字节;其由三部分组成,分别是行3-行7、行8-行12、行13、行28;其中前二者分别是基类Base1、Base2的布局,最后的行28为虚拟基类Common的布局。

以基类Base1部分为例,可发现其由一个虚函数表指针vftable和虚基表指针vbtable构成,先看Base1部分的vftable所指向的虚表$vftable@Base1(行19),不难发现,其中的表项2已经被Derived::print2给override了;再来看Base2部分的vftable所指向的虚表$vftable@Base2(行23),可发现,同样的,Base2::dump2被Derived::dump2给override了。这不明摆着就是虚函数机制嘛,heh~

值得注意的是,这个例子同时说明,多继承场合下,其实在单一对象中是存在多个this指针的....行35-36给出了如何将Derived的this指针校正为其基类子对象this指针的偏移量,也就是说,根据行36,假设有个Derived d,那么d.dump1()实际上应该理解成通过虚表$vftable@Base2对((Base2*)(((char*)&d)+12))->dump1()的调用....即传递给所有Base2成员函数的this指针应该是(Base2*)((char*)(&d)+12),这里可能我写得恐怖了点,意思到了就成....这不,普通继承、多继承、对象Slicing的语义都在这个布局里面了,看仔细了哈~

OK,多继承看完了,继续看虚拟基类是如何布局的。虚基Common在Derived的布局中,位于Derived本身数据成员之后的位置。Base1、Base2中均保存了一个vbtable指针,其分别指向各自所使用的虚基表$vbtable@Base1$vbtable@Base2,为什么要指向一个虚基表? 很简单,因为Base1、Base2有可能会同时继承多个不同的虚拟基类.....这充分体现了C++对象布局的复杂性....在每个虚基表中,保存了所继承的虚拟基类部分相对于子类部分vbtable指针的偏移值,以Base2为例,我们知道Base2的vbtable在Derived中的偏移值为16(行10),则根据$vbtable@Base2,虚基Common部分距离Base2 vbtable指针的偏移值为12,则有虚基Common在Derived中的总偏移值为16+12。与普通多继承同理,我们在调用非虚拟的虚基成员函数时,必须将Derived的this指针调整为指向虚基部分的this指针,只有这样才能成功地访问虚基自身的数据成员和虚基的虚拟函数(通过虚基自己的vftable,为简单起见,上例中我就没弄那么复杂了,大家可以自己玩玩,明白如何举一反三即可)

看完了上述解释,是不是感觉比啃Inside C++ Object Model来得更快更直观啊?

http://blog.sina.com.cn/s/blog_a401a1ea010174rj.html

VC++对象布局的奥秘:虚函数、多继承、虚拟继承的更多相关文章

  1. c++ 对象内存分配和虚函数

    1. c++类对象(不含虚函数)在内存中的分布 c++类中有四种成员:静态数据.非静态数据.静态函数.非静态函数. 1. 非静态数据成员放在每个对象内部,作为对象专有的数据成员 2. 静态数据成员被抽 ...

  2. Visual C++ 8.0对象布局的奥秘:虚函数、多继承、虚拟继承(VC直接输出内存布局)

    原文:VC8_Object_Layout_Secret.html 哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleCl ...

  3. C++中继承 声明基类析构函数为虚函数作用,单继承和多继承关系的内存分布

    1,基类析构函数不为虚函数 #include "pch.h" #include <iostream> class CBase { public: CBase() { m ...

  4. C++面向对象编程之虚函数与多态和继承和复合下的构造和析构

    1.对于非虚函数,是不希望派生类对该函数重新定义: 对于virtual函数,在父类已经有默认定义后,并希望子类重新定义它: 对于pure virtual函数,父类没有默认定义,派生类必须要重新定义它: ...

  5. C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现

    tfref 前言 C++对象的内存布局 只有数据成员的对象 没有虚函数的对象 拥有仅一个虚函数的对象 拥有多个虚函数的对象 单继承且本身不存在虚函数的继承类的内存布局 本身不存在虚函数(不严谨)但存在 ...

  6. C++学习7-面向对象编程基础(多态性与虚函数、 IO文件流操作)

    多态 多态性是指对不同类的对象发出相同的消息将返回不同的行为,消息主要是指类的成员函数的调用,不同的行为是指不同的实现: 函数重载 函数重载是多态性的一种简单形式,它是指允许在相同的作用域内,相同的函 ...

  7. Vc++内存布局

    Vc++内存布局 测试平台 Windows server 2012 R2 and visual studio 2013 professional. 本篇文章意在介绍vc++中类的内存布局方式,只是研究 ...

  8. C++多态性:虚函数的调用原理

    多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数. 一个函数说明为虚函数,表明在继承的类中重载这个函数时 ...

  9. 浅谈C++虚函数机制

    0.前言 在后端面试中语言特性的掌握直接决定面试成败,C++语言一直在增加很多新特性来提高使用者的便利性,但是每种特性都有复杂的背后实现,充分理解实现原理和设计原因,才能更好地掌握这种新特性. 只要出 ...

随机推荐

  1. PDFium-PDF开源之旅(1)-初探代码下载编译

    近日,Google和Foxit合作开源了Foxit的PDF源代码. 叫PDFium 相关新闻不少.哈,虽说已经不是程序猿了.只是还是有兴趣跑起来围观看看.废话少说.先说编译代码的步骤(事实上Googl ...

  2. CSS换行2

    1.可以使用强制换行符号<br />换行.如果在一个文章里可以在文章需要换行的地方加入<br />即可实现自动换行-常说的小换行,与换行前没有间隔.实例如下图 换行说明图无间隔 ...

  3. JavaScript 深入学习及常用工具方法整理 ---- 01.浮点数

    在JavaScript中是不区分整数值和浮点数值的,其中所有的数字均用浮点数值表示.JavaScript采用IEEE 754标准(有兴趣可以浏览网络规范分类下的IEEE 754标准,需要原文件请在留言 ...

  4. sysctl: command not found

    在安装RedHat5.9时没有在安装时定制软件包,在后面使用sysctl命令时提示: -bash: sysctl: command not found 找了半天原来是还需要安装: rpm -ivh p ...

  5. slots

    class Student(object): pass s = Student() s.name = 'Michael' print(s.name) def set_age(self, age): s ...

  6. Arduino 时钟模块(clock module) DS1306

    http://www.pjrc.com/teensy/td_libs_DS1307RTC.html 下载相关的库程序 连接: DS1306: 1.接3.3V 2.SDA接A4 3.SCL接A5 读取: ...

  7. 修改spfile导致oracle无法启动

    今天尝试着调整一下数据库的性能,由于疏忽没有做好备份(spfile.ora),直接修改了MEMORY_TARGET参数,alter system set MEMORY_TARGET=4096m;导致重 ...

  8. 合理设计C代码 函数笔记

    本文首先用判断一个数字是否为素数使用For循环实现做例子,之后用函数完成多个功能. 什么是素数? 素数又成质数,不包含1和0.通俗的去说就是它除了能表示为它自己和1的乘积以外,不能表示为任何其它两个整 ...

  9. LintCode-三数之和

    题目描述: 给出一个有n个整数的数组S,在S中找到三个整数a, b, c,找到所有使得a + b + c = 0的三元组. 注意事项 在三元组(a, b, c),要求a <= b <= c ...

  10. xfire发布的Webservice中Spring注入为空的解决方案

    Spring框架使用中注入为空是一个比较头疼的问题,遇到Webservice和Spring框架配合时,这个问题更容易出现并很难发现问题的原因. 在做SSO系统中就遇到这样的问题,在Service的实现 ...