当继承为公共虚拟时,为什么类的大小会增加?

时间:2014-03-23 10:48:36

标签: c++

为什么类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;预期的行为。

5 个答案:

答案 0 :(得分:2)

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

  

$ 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

// class b vptr和size information

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和size information

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和size information

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。

答案 1 :(得分:1)

使用非虚拟继承时,在编译时确定对象的完整布局。使用虚拟继承时不是这种情况 - 在这种情况下,基本子对象的偏移量在运行时确定。

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

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

答案 2 :(得分:0)

Stroustrup很好地描述了这一点 Multiple Inheritance for C++第7.1节。

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

它不会添加vtable。

答案 3 :(得分:0)

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

这里有很多“可能”,这是因为整个事情完全是编译器特定的,因为C ++标准没有为类指定内存布局。很难找到关于互联网上编译器供应商的虚拟基类内存布局的非常好的 authorative 文档(这可能是因为虚拟继承首先是很少使用的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实现虚拟继承,并且在不需要时不关心它的优化。

答案 4 :(得分:-1)

我猜测B对于vtable来说更大(即使没有函数)。

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

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

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