虚函数/虚继承对对象内存大小的影响,凌乱中......求指引 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
codercai
V2EX    C

虚函数/虚继承对对象内存大小的影响,凌乱中......求指引

  •  
  •   codercai 2015-06-22 11:35:37 +08:00 2328 次点击
    这是一个创建于 3764 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class A { public: int a; virtualvoid f1(){} virtual void f2(){} }; class E : public A { public: void virtual f3(){} }; class F : virtual public A { }; class G : virtual public A { void virtual f4(){} }; int main() { cout<<sizeof(A)<<endl;//8 cout<<sizeof(E)<<endl;//8  cout<<sizeof(F)<<endl;//12 cout<<sizeof(G)<<endl;//12 } 

    如上,私以为:
    1、显然,A中因为虚函数的存在,增加了一个指向虚表的指针,所以大小为4(int a)+ 4(指针)=8
    2、从E的大小可以看出,一般派生时,派生类中增加虚函数并没有导致派生类变大,这说明派生类 和基类应该是公用了同一张虚函数表,他们的虚函数地址都放在里面,所以没有必要在派生类中 同样增加一个指针指向虚表。因此E的大小不变。
    3、从F的大小可以看出,虚继承时,派生类中会增加一个指针指向他的父类,因此派生类的大小增 加4字节。
    4、class G 是为了进一步印证2、3点。

    所以我是这么总结的:
    1、如果基类、派生类均含有虚函数,他们是公用一张虚表的
    2、虚继承会在派生类中额外增加一个指向父类的指针

    然而,有这么一条博文说基类派生类有各自不同的虚表,和我的推论相悖,但却视乎证据确凿,我竟无言以对,所以凌乱了。求各位指点啊。http://blog.csdn.net/kangroger/article/details/38313461

    10 条回复    2015-06-23 08:56:35 +08:00
    ini
        1
    ini  
       2015-06-22 12:48:33 +08:00
    ilotuo
        2
    ilotuo  
       2015-06-22 13:25:00 +08:00 via Android
    应该是继承的时候复制了基类的虚函数表吧。
    每个类有自己的虚函数表,只不过根据有没有重新实现的话改变里面函数指针的值。
    有本书叫 深入探索cpp对象模型
    josephpei
        3
    josephpei  
       2015-06-22 14:04:30 +08:00
    Lippman 《Inside C++ Object Model》深度探索C++对象模型,有详细解释。
    codercai
        4
    codercai  
    OP
       2015-06-22 16:40:29 +08:00
    @ini
    非常感谢层煮的分享!
    博主的文章基本能看懂,那么问题还是存在的呀,如果确实是基类派生类各自有其虚表,那么就是个自有一个虚表指针,这样的话,上面的输出就说不通了,应该是
    cout<<sizeof(E)<<endl;//12。
    这个作何解释呀?
    secondwtq
        5
    secondwtq  
       2015-06-22 18:11:58 +08:00
    @codercai 我细节很多地方都忘了,不过这块还是有点记忆的。

    无论是基类还是派生类的对象实例,都只会有一个 vptr,占据对象布局中相同的位置。
    所不同的只是其指向的虚表。

    你想啊,加一个继承层次,就在其所有实例对象上加一个 vptr,这样做,和所有需要 vtable 的实例对象都有且只有一个 vptr,哪个成本低。
    况且还有一个关键问题就是,如果子类实例对象有多个 vptr(单继承),当你使用基类指针调用虚函数的时候,是根本没有办法判断该指针所指对象是不是拥有楼主所推断出的“多出来”的那个 vptr 的。

    实际情况是,基类和派生类的对象实例,都有且只有一个 vptr,并占据对象布局中相同的位置(当然是有虚函数的时候)。而在不同类型的对象实例中,这个指针指向不同的 vtable。

    举个栗子,如果你知道了不同类型对象虚表的地址,在你对 C++ 搞出来的可执行程序做动态汇编调试的时候,找到一个对象的位置,那么在 hex view 里面瞄一眼这个对象的开始几个字节(vptr 的位置),就能知道这个对象是什么类型,我管这个叫人肉 RTTI :)

    C++ 运行期多态的精髓,窃以为就在这个虚指针上。另外我个人一般是把这个 vptr 当作一个 data member 来看的。
    secondwtq
        6
    secondwtq  
       2015-06-22 18:19:13 +08:00
    @codercai 另外,之所以基类和派生类不共用同一张虚函数表,我个人认为是因为同一个基类可能会派生出多个子类,并且它们可能会增加、覆盖不同的虚函数。

    而在进行虚函数调用的时候,你所拥有的信息只有:一个 vptr 及该 vptr 指向的 vtable;类型定义;指针/引用的类型(不一定是对象实际类型,但是就算不是也一定是其基类);由类型信息所推导出的,编译器写入可执行代码的一个索引。

    这些信息,除了第一项是运行时的,后三项全部是编译时确定的。我觉得如果共用一张 vtable 的话,在以上条件下,是无法处理第一段所描述的复杂情况的。
    codercai
        7
    codercai  
    OP
       2015-06-22 19:15:09 +08:00
    @secondwtq
    又查了一些资料,加上层所言,基类派生类确实是各有其虚表,那么问题就是更奇怪了,为什么输出是这样的呢?
    cout<<sizeof(A)<<endl;//8
    cout<<sizeof(E)<<endl;//8
    然而不应该是
    cout<<sizeof(A)<<endl;//8
    cout<<sizeof(E)<<endl;//12
    这样的么? 都应该有一个指向虚表的指针啊~~~
    secondwtq
        8
    secondwtq  
       2015-06-23 01:51:16 +08:00
    @codercai 你也许把“子类实例是子类的父类部分与子类部分所拼接起来的”和这个搞混了
    子类和父类实例使用不同的 vtable,其 vptr 的值也相应是不一样的。但是每个实例中只有一个 vptr(对于单继承来说是这样的)。

    可以这样说,vtable 不能简单被分为“基类”和“子类”两个部分,一个对象的 vptr 所指 vtable,其中包含了该对象实际类型中所有可以用指针/引用直接调用虚函数(包括父类中未被子类 override 掉的,子类新定义的,和子类 override 掉父类的)。因此任何一个实例,只有一个 vptr 的位置(依然是只针对单继承)。

    我个人平常的理解是,这个 vptr 相当于对象的一个“类型信息”或者类型的 ID,据我所知某些动态语言的实现中,对象实际在内存中的大小是不定的,但是每个对象头部的结构是确定的,这个头部中存储了必要的类型信息,据此可推导出对象的具体“类型”,以及该对象究竟符合哪一种布局。

    C++ 中一个实例对象的实际类型可能有好几层继承,涉及到若干个基类,但是其类型总是能唯一确定的。
    skydiver
        9
    skydiver  
       2015-06-23 03:09:06 +08:00 via iPad
    @livid 为什么这个主题页面也需要登录才能查看。。
    codercai
        10
    codercai  
    OP
       2015-06-23 08:56:35 +08:00
    @secondwtq
    读到你的第一句就恍然大悟,非常感谢啊,这个问题困扰我一两天了。我确实是将基类vptr单纯继承到了子类了,结合前面几位
    @ini
    @ilotuo
    给的博文链接和你的解答,现在已经非常清晰了,感谢~~~~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3676 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 10:21 PVG 18:21 LAX 03:21 JFK 06:21
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86