Firefox 3
昵称:neoragex2002
园龄:7年2个月
粉丝:11
关注:1

搜索

 

常用链接

随笔分类

文章分类

博客链接

站点链接

最新评论

阅读排行榜

评论排行榜

哈哈,从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来得更快更直观啊?heh
posted on 2007-11-01 07:37 neoragex2002 阅读(2077) 评论(12) 编辑 收藏

FeedBack:
#1楼 2007-11-28 10:30 小名阿铁      
Output里面有如下字样:

汗,居然能看到这样的结果.
害我之前还搞很多测试用例,然后sizeof,来学习对象模型.
谢谢了.

 回复 引用 查看   
#2楼 2007-11-28 10:44 小名阿铁      
看完了上述解释,是不是感觉比啃Inside C++ Object Model来得更快更直观啊?hehe
的确,那本书很深奥,我看了三遍,但还是没能掌握好.主要是各方的实现有差别,无法做到放之天下而皆准.
我发现cl -Fa ***.cpp产生后的汇编代码也不错,可惜我汇编差了.

 回复 引用 查看   
#3楼[楼主2007-11-28 11:23 neoragex2002      
@小名阿铁
Inside C++ Object Model此书我感觉不佳,因为作者始终在避免涉及编译器实现的相关性,所以关键地方总是笼统含糊,但实际上,C++对象模型实现本身就是一个编译器实现相关的问题,逃避这个问题那就是隔鞋挠痒。因此,我觉得比较合适的方式还是紧密围绕一个编译器,比方说VC8,来考察其对象模型实现。这样一遍下来,反倒能迅速搞懂许多实实在在的东西。

单纯看-Fa输出的话,即使汇编底子很好也比较累,而且杂合了许多其他因素在里面,很容易不得要领。有了上文方法,至少可以省却一半以上的时间,而且覆盖面极广,我在文中便已浓缩了虚函数、虚基、普通继承、多继承、虚基多继承、对象切割等6个方面,呵呵,自己多玩玩,说不定会有更多体会:)

 回复 引用 查看   
#4楼 2007-11-28 11:39 小名阿铁      
@neoragex2002
大哥的回复说得很好!
比如:"关键地方总是笼统含糊"
深有感触
VS2005能"玩"这个么?我没安装2008,不知道它对标准C++支持如何?
感觉2005比VC6.0好多了,已经很不错.
(平时主要用简单的IDE,比如Dev-CPP,Code::blocks(因为我不做项目,纯粹学语言和win32,用这些够了,但以后要融入搞开发的团队中,效率第一,就得使用高级点的了))
//修改:嘿嘿,我才用笔算了算(走亲戚,这里没安装环境哈),得出Derived大小是28个字节!原来按照的是另外一种实现方式(g++)
//深度探索里的确说过,微软的实现是单独在对象里添加一个指针vbtr,而不是把这个指针放在vftbl(虚函数表里)

 回复 引用 查看   
#5楼[楼主2007-11-28 11:46 neoragex2002      
呵呵,我现在还没装VS2008呢,VC8就是VS2005里面的VC了:)
 回复 引用 查看   
#6楼 2007-11-28 12:04 小名阿铁      
请教:派生类虚成员函数内部调用父类虚成员函数是否产生临时对象
http://www.cnblogs.com/irons/archive/2007/11/28/975150.html

 回复 引用 查看   
#7楼[楼主2007-11-28 12:14 neoragex2002      
你觉得呢?heh,这个问题是不是有点太plain了,A::heiheihei()只是一个静态绑定而已,在B::heiheihei里面你已经知道this了,在单继承情况下,B的this可以很容易地向上映射成基类A的this,甚至无需进行任何displacement调整,那么我们是否还有必要生成一个临时对象来调A::heiheihei()呢?其实这种代码在一般情况下是极其常见的,如MFC中的overrides,不必考虑编译器差异,呵呵。或者,你还是比较注重实证,那么ok,给A和B添加上拷贝构造函数,分别设个断点,运行一下便知道有没有临时对象产生了,也不必看汇编了。

class A
{
public:
       
virtual void heiheihei()
       {
         cout 
<< "A" << endl;
       }
};

class B :   public A
{
public:
       
virtual void heiheihei()
       {
         A::heiheihei();
         cout 
<< "B" << endl;
       }
};

 回复 引用 查看   
#8楼 2008-03-22 17:15 Isatin[未注册用户]
Inside C++ Object Model裡總是建議不要在virtual base class中宣告nonstatic data member,請問明確的理由是?
 回复 引用   
#9楼[楼主2008-03-22 19:04 neoragex2002      
@Isatin
这是个多继承(MI)的问题。所以从MI角度来解释最合适,这里我给出两个最为主要的理由:

1. MI编译时:
先从一个例子讲起:a是虚基,b和c分别继承了a,然后d又继承了b和c,假设a中有非静态数据成员 m,而且b/c的成员初始化列表中都调用了a的构造函数来初始化m。现在的问题是,在d初始化之前,a::m究竟被初始化了多少次?2次初始化a::m合不合理?如果是被初始化了2次的话,那么a::m究竟是谁先初始化谁后初始化的呢?d的构造函数看到的究竟是谁初始化的a::m值?是b还是c?

这里我就不沿着错误的方向继续推了。直接给答案。C++标准认为,多次初始化虚基数据成员是不合理的,因为这样会导致谁先初始谁后初始的混乱局面。因此,虚基类数据成员只能被初始化一次,即,虚基类构造函数的参数始终由继承结构中的最底层派生类成员初始化列表指定,每一个继承自虚基类的子类都必须在成员初始化列表中调用虚基类构造函数,而不管这个子类在继承图中与虚基类相距有多远,哪怕再远,哪怕这个虚基类在该子类的父类中是以protected或private方式继承的,均不例外。比方说上面那个例子,正确答案是d必须自己承担起a::m初始化的责任来,而且,d对a的初始化必须是先于b和c的初始化的,同时,在d::b和d::c的初始化过程中,其成员初始化列表中虚基a构造函数的调用将被忽略。这样就保证了d::a::m只会被初始化一次。事实就是这么混乱,heh

好了,下面可以回答你的问题了。从上述虚基初始化规则可见,这个规则实现起来麻烦倒还是其次,而且有时根本不具可实施性。比方说,a、b、c同存在于一个类库中,但a是一个非公开的内部实现,而d仅仅只是类库的使用者,d怎么可能知道怎样去初始化a呢?在这种场合下,那个虚基初始化规则简直不可理喻。因此,最好的解决方案就是不要在虚基类中定义任何非静态数据成员,干脆省了虚基初始化,图个清爽。这是理由之一。

2. MI运行时:
理由之二,见本文关于MI对象布局的描述。MI虚基类对象布局过分复杂,引用虚基数据成员时,频繁的this偏移调整、间接取址操作将显著降低运行时执行效率,这可类比虚函数开销,而且只多不少。因此,无论是编译时还是运行时,在虚基中定义非静态数据成员都不划算,所以应当尽量避免。

 回复 引用 查看   
#10楼 2008-04-03 00:48 VSWECE[未注册用户]
VS2003+SP1好像不支持列 /d1reportSingleClassLayoutXXX
 回复 引用   
#11楼[楼主2008-04-03 18:05 neoragex2002      
vs2003里面是vc++ 7.1,非8.0
 回复 引用 查看   
#12楼 2010-02-01 16:00 烛秋      
厉害啊楼主,学习了。
 回复 引用 查看