V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
codercai
V2EX  ›  C

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

  •  
  •   codercai · 2015-06-22 11:35:37 +08:00 · 1860 次点击
    这是一个创建于 3443 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class A
    {
    public:
        int a;
        virtual void 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   ·   实用小工具   ·   2590 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 15:35 · PVG 23:35 · LAX 07:35 · JFK 10:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.