【问题标题】:Why the size of class is increasing when inherit as public virtual? [duplicate]为什么继承为公共虚拟时类的大小会增加? [复制]
【发布时间】:2014-04-30 15:42:57
【问题描述】:

为什么bc 类的大小是4?虚拟关键字是否创建了一个 vptr(是否存在没有虚拟功能的 vptr?)还是其他什么?请分享您对此的看法。

#include<iostream>
using namespace std;


class a{    
};

class b:public virtual a{
};

class c:public virtual a{
};

class d:public  b, public c{
};

main(){
    cout<<sizeof(a)<<"\n"; //1
    cout<<sizeof(b)<<"\n"; //4
    cout<<sizeof(c)<<"\n"; //4
    cout<<sizeof(d)<<"\n"; //8
}

如果virtual 未在任何地方使用,则o/p 变为:1 1 1 2;预期行为。

【问题讨论】:

  • @jerk 这里没有虚函数。
  • 这是错字还是你叫我混蛋? :)
  • 天啊!!!我很抱歉......这只是一个错字:)

标签: c++


【解决方案1】:

我猜 vtable 的 B 更大(即使没有函数)。

当然,实现将取决于编译器和所有这些,但我记得在过去的 Delphi 中,它会在类的实例前 4 个字节创建虚拟表(例如 &myInstance-4)。

我猜即使没有函数也必须创建一个表,也许只是在其中放一个 0。

请记住,处理虚拟函数的方式必须在整个系统中兼容,因此如果您要加载查看类的动态库,则需要知道如何读取它们。由于这个原因,我怀疑编译器无法完全摆脱多余的空间。

【讨论】:

    【解决方案2】:

    当使用非虚拟继承时,对象的完整布局是在编译时确定的。使用虚拟继承时情况并非如此——在这种情况下,基础子对象的偏移量是在运行时确定的。

    如何实现这一点的细节因编译器而异,但通常会涉及一个或多个附加指针。一种解释见this question and it's answer

    请注意,如果您有虚拟方法,这与需要的 vtable 指针是分开的。正如您在示例中指出的那样,您的示例中没有虚拟方法。

    【讨论】:

      【解决方案3】:

      Stroustrup 在 Multiple Inheritance for C++ 第 7.1 节。

      表示虚拟基类a对象的对象不能相对于两者放置在固定位置 bc 在所有对象中。因此,指向a 的指针必须存储在所有直接访问a 的对象中 对象以允许独立于其相对位置的访问。

      它不添加 vtable。

      【讨论】:

        【解决方案4】:

        是的,由于虚拟继承,即使没有虚拟函数,编译器也会创建 vptr。为了理解使用 gcc 编译器,我们可以使用 (-fdump-tree-all) 标志并查看可以找到 vptr 和 vtable 布局的中间文件 (*.class)。

        $ g++ -fdump-tree-all -Wall basic.cpp -o basic

        现在我们可以从中间的 basic.class 文件中找到关于 vptr 和 vtable 布局的信息。

        // 类信息

        Class a
           size=1 align=1
           base size=0 base align=1
        a (0x0x7fc8d707e2a0) 0 empty
        

        //b类vptr和大小信息

        Vtable for b
        b::_ZTV4b: 3u entries
        0     0u
        8     (int (*)(...))0
        16    (int (*)(...))(& _ZTI4bbbb)
        
        VTT for b
        b::_ZTT4b: 1u entries
        0     ((& b::_ZTV4b) + 24u)
        
        Class b
           size=8 align=8
           base size=8 base align=8
        b (0x0x7fc8d7053e38) 0 nearly-empty
            vptridx=0u vptr=((& b::_ZTV4bbbb) + 24u)
          a (0x0x7fc8d707e300) 0 empty virtual
              vbaseoffset=-24
        

        //class c vptr和大小信息

        Vtable for c
        c::_ZTV4c: 3u entries
        0     0u
        8     (int (*)(...))0
        16    (int (*)(...))(& _ZTI4cccc)
        
        VTT for c
        c::_ZTT4c: 1u entries
        0     ((& c::_ZTV4c) + 24u)
        
        Class c
           size=8 align=8
           base size=8 base align=8
        c (0x0x7fc8d7053ea0) 0 nearly-empty
            vptridx=0u vptr=((& c::_ZTV4c) + 24u)
          a (0x0x7fc8d707e360) 0 empty virtual
              vbaseoffset=-24
        

        //class d vptr和大小信息

        Vtable for d
        d::_ZTV4d: 6u entries
        0     0u
        8     (int (*)(...))0
        16    (int (*)(...))(& _ZTI4d)
        24    18446744073709551608u
        32    (int (*)(...))-8
        40    (int (*)(...))(& _ZTI4d)
        
        Construction vtable for b (0x0x7fc8d70f8000 instance) in d
        d::_ZTC4d0_4b: 3u entries
        0     0u
        8     (int (*)(...))0
        16    (int (*)(...))(& _ZTI4b)
        
        Construction vtable for c (0x0x7fc8d70f8068 instance) in d
        d::_ZTC4d8_4c: 3u entries
        0     18446744073709551608u
        8     (int (*)(...))0
        16    (int (*)(...))(& _ZTI4c)
        
        VTT for d
        d::_ZTT4d: 4u entries
        0     ((& d::_ZTV4d) + 24u)
        8     ((& d::_ZTC4d0_4b) + 24u)
        16    ((& d::_ZTC4d8_4c) + 24u)
        24    ((& d::_ZTV4d) + 48u)
        
        Class d
           size=16 align=8
           base size=16 base align=8
        d (0x0x7fc8d70cca80) 0
            vptridx=0u vptr=((& d::_ZTV4d) + 24u)
          b (0x0x7fc8d70f8000) 0 nearly-empty
              primary-for d (0x0x7fc8d70cca80)
              subvttidx=8u
            a (0x0x7fc8d707e3c0) 0 empty virtual
                vbaseoffset=-24
          c (0x0x7fc8d70f8068) 8 nearly-empty
              subvttidx=16u vptridx=24u vptr=((& d::_ZTV4d) + 48u)
            a (0x0x7fc8d707e3c0) alternative-path
        

        这解释了这里发生了什么以及为什么以及如何对象大小会根据创建的 vptr 数量而变化。我的机器是 x86_64 GNU/Linux,因此指针大小将是 8 而不是原始示例中的 4。

        【讨论】:

          【解决方案5】:

          Scott Meyers 的《更有效的 C++》,第 24 条,“了解虚函数、多重继承、虚基类和 RTTI 的成本”,虽然没有深入探讨这个问题,但解释了如何虚拟基类可以通过在两个直接派生类中为虚拟基类添加指针来增加对象大小。它可能与虚函数没有任何关系。编译器可能会发现这是解决bc 本身可能具有不同大小的问题的最简单方法,并且a-成员访问可以通过bcd 中的任何一个进行。

          这里有很多“可能”,这是因为整个事情完全是特定于编译器的,因为 C++ 标准没有为类指定内存布局。奇怪的是,在 Internet 上很难找到关于编译器供应商的虚拟基类的内存布局的真正好的权威文档(这可能是因为虚拟继承首先是一个很少使用的 C++ 语言特性)。

          对于 GCC,您可能会发现 -fdump-class-hierarchy 编译器选项很有用。这是它在我的机器上为您的示例生成的内容(删除标准库的内容):

          Class a
             size=1 align=1
             base size=0 base align=1
          a (0x0x344a0a8) 0 empty
          
          Vtable for b
          b::_ZTV1b: 3u entries
          0     0u
          4     (int (*)(...))0
          8     (int (*)(...))(& _ZTI1b)
          
          VTT for b
          b::_ZTT1b: 1u entries
          0     ((& b::_ZTV1b) + 12u)
          
          Class b
             size=4 align=4
             base size=4 base align=4
          b (0x0x3460b40) 0 nearly-empty
              vptridx=0u vptr=((& b::_ZTV1b) + 12u)
            a (0x0x344a0e0) 0 empty virtual
                vbaseoffset=-12
          
          Vtable for c
          c::_ZTV1c: 3u entries
          0     0u
          4     (int (*)(...))0
          8     (int (*)(...))(& _ZTI1c)
          
          VTT for c
          c::_ZTT1c: 1u entries
          0     ((& c::_ZTV1c) + 12u)
          
          Class c
             size=4 align=4
             base size=4 base align=4
          c (0x0x3460d40) 0 nearly-empty
              vptridx=0u vptr=((& c::_ZTV1c) + 12u)
            a (0x0x344a118) 0 empty virtual
                vbaseoffset=-12
          
          Vtable for d
          d::_ZTV1d: 6u entries
          0     0u
          4     (int (*)(...))0
          8     (int (*)(...))(& _ZTI1d)
          12    4294967292u
          16    (int (*)(...))-4
          20    (int (*)(...))(& _ZTI1d)
          
          Construction vtable for b (0x0x3460f00 instance) in d
          d::_ZTC1d0_1b: 3u entries
          0     0u
          4     (int (*)(...))0
          8     (int (*)(...))(& _ZTI1b)
          
          Construction vtable for c (0x0x3460f40 instance) in d
          d::_ZTC1d4_1c: 3u entries
          0     4294967292u
          4     (int (*)(...))0
          8     (int (*)(...))(& _ZTI1c)
          
          VTT for d
          d::_ZTT1d: 4u entries
          0     ((& d::_ZTV1d) + 12u)
          4     ((& d::_ZTC1d0_1b) + 12u)
          8     ((& d::_ZTC1d4_1c) + 12u)
          12    ((& d::_ZTV1d) + 24u)
          
          Class d
             size=8 align=4
             base size=8 base align=4
          d (0x0x3460ec0) 0
              vptridx=0u vptr=((& d::_ZTV1d) + 12u)
            b (0x0x3460f00) 0 nearly-empty
                primary-for d (0x0x3460ec0)
                subvttidx=4u
              a (0x0x344a150) 0 empty virtual
                  vbaseoffset=-12
            c (0x0x3460f40) 4 nearly-empty
                subvttidx=8u vptridx=12u vptr=((& d::_ZTV1d) + 24u)
              a (0x0x344a150) alternative-path
          

          对于非虚拟继承,内存布局会简单很多:

          Class a
             size=1 align=1
             base size=0 base align=1
          a (0x0x344a0a8) 0 empty
          
          Class b
             size=1 align=1
             base size=1 base align=1
          b (0x0x346cb40) 0 empty
            a (0x0x344a0e0) 0 empty
          
          Class c
             size=1 align=1
             base size=1 base align=1
          c (0x0x346cbc0) 0 empty
            a (0x0x344a118) 0 empty
          
          Class d
             size=2 align=1
             base size=2 base align=1
          d (0x0x346cc40) 0 empty
            b (0x0x346cc80) 0 empty
              a (0x0x344a150) 0 empty
            c (0x0x346ccc0) 1 empty
              a (0x0x344a188) 1 empty
          

          所以 GCC 显然是通过 vptrs 实现虚拟继承,并不关心在不需要时将其优化掉。

          【讨论】:

            猜你喜欢
            • 2019-10-11
            • 1970-01-01
            • 2018-11-12
            • 2017-04-20
            • 2013-06-21
            • 2021-07-05
            • 2014-03-31
            • 2023-03-22
            • 1970-01-01
            相关资源
            最近更新 更多