简体   繁体   English

带有虚拟方法的C ++对象大小

[英]C++ object size with virtual methods

I have some questions about the object size with virtual. 我对虚拟对象的大小有些疑问。

1) virtual function 1)虚函数

class A {
    public:
       int a;
       virtual void v();
    }

The size of class A is 8bytes....one integer(4 bytes) plus one virtual pointer(4 bytes) It's clear! A类的大小为8字节。...一个整数(4字节)加一个虚拟指针(4字节)很明显!

class B: public A{
    public:
       int b;
       virtual void w();
}

What's the size of class B? B班的人数是多少? I tested using sizeof B, it prints 12 我使用sizeof B测试,它打印12

Does it mean that only one vptr is there even both of class B and class A have virtual function? 这是否意味着即使B类和A类都具有虚拟功能,也只有一个vptr? Why there is only one vptr? 为什么只有一个vptr?

class A {
public:
    int a;
    virtual void v();
};

class B {
public:
    int b;
    virtual void w();
};

class C :  public A, public B {
public:
    int c;
    virtual void x();
};

The sizeof C is 20........ C的大小是20 ........

It seems that in this case, two vptrs are in the layout.....How does this happen? 似乎在这种情况下,布局中有两个vptr .....如何发生? I think the two vptrs one is for class A and another is for class B....so there is no vptr for the virtual function of class C? 我认为这两个vptr一个用于类A,另一个用于类B ....所以没有用于类C的虚函数的vptr?

My question is, what's the rule about the number of vptrs in inheritance? 我的问题是,继承中vptrs的数量规则是什么?

2) virtual inheritance 2)虚拟继承

    class A {
    public:
        int a;
        virtual void v();
    };

    class B: virtual public A{                  //virtual inheritance 
    public:
        int b;
        virtual void w();
    };

    class C :  public A {                      //non-virtual inheritance
    public:
        int c;
        virtual void x();
    };

class D: public B, public C {
public:
    int d;
    virtual void y();
};

The sizeof A is 8 bytes -------------- 4(int a) + 4 (vptr) = 8 A的大小为8个字节-------------- 4(int a)+ 4(vptr)= 8

The sizeof B is 16 bytes -------------- Without virtual it should be 4 + 4 + 4 = 12. why there is another 4 bytes here? B的大小是16个字节--------------如果没有虚拟字节,它应该是4 + 4 + 4 =12。为什么这里还有另外4个字节? What's the layout of class B ? B类的布局是什么?

The sizeof C is 12 bytes. C的大小为12个字节。 -------------- 4 + 4 + 4 = 12. It's clear! -------------- 4 + 4 + 4 =12。很明显!

The sizeof D is 32 bytes -------------- it should be 16(class B) + 12(class C) + 4(int d) = 32. Is that right? D的大小是32个字节--------------它应该是16(B类)+ 12(C类)+4(int d)=32。对吗?

    class A {
    public:
        int a;
        virtual void v();
    };

    class B: virtual public A{                       //virtual inheritance here
    public:
        int b;
        virtual void w();
    };

    class C :  virtual public A {                    //virtual inheritance here
    public:
        int c;
        virtual void x();
    };

  class D: public B, public C {
   public:
        int d;
        virtual void y();
    };

sizeof A is 8 A的size为8

sizeof B is 16 B的大小是16

sizeof C is 16 C的大小是16

sizeof D is 28 Does it mean 28 = 16(class B) + 16(class C) - 8(class A) + 4 ( what's this? ) D的sizeof是28表示28 = 16(B类)+ 16(C类)-8(A类)+ 4(这是什么?)

My question is , why there is an extra space when virtual inheritance is applied? 我的问题是,为什么在应用虚拟继承时会有额外的空间?

What's the underneath rule for the object size in this case? 在这种情况下,对象大小的基本规则是什么?

What's the difference when virtual is applied on all the base classes and on part of the base classes? 将virtual应用于所有基类和部分基类有什么区别?

This is all implementation defined. 这是所有实现的定义。 I'm using VC10 Beta2. 我正在使用VC10 Beta2。 The key to help understanding this stuff (the implementation of virtual functions), you need to know about a secret switch in the Visual Studio compiler, /d1reportSingleClassLayoutXXX . 帮助理解这些东西(虚拟功能的实现)的关键是,您需要了解Visual Studio编译器中的秘密开关/ d1reportSingleClassLayoutXXX I'll get to that in a second. 一会儿,我会讲到。

The basic rule is the vtable needs to be located at offset 0 for any pointer to an object. 基本规则是,对于任何指向对象的指针,vtable必须位于偏移量0处。 This implies multiple vtables for multiple inheritance. 这意味着用于多个继承的多个vtable。

Couple questions here, I'll start at the top: 这里有几个问题,我将从顶部开始:

Does it mean that only one vptr is there even both of class B and class A have virtual function? 这是否意味着即使B类和A类都具有虚拟功能,也只有一个vptr? Why there is only one vptr? 为什么只有一个vptr?

This is how virtual functions work, you want the base class and derived class to share the same vtable pointer (pointing to the implementation in the derived class. 这是虚拟函数的工作方式,您希望基类和派生类共享相同的vtable指针(指向派生类中的实现)。

It seems that in this case, two vptrs are in the layout.....How does this happen? 似乎在这种情况下,布局中有两个vptr .....如何发生? I think the two vptrs one is for class A and another is for class B....so there is no vptr for the virtual function of class C? 我认为这两个vptr一个用于类A,另一个用于类B ....所以没有用于类C的虚函数的vptr?

This is the layout of class C, as reported by /d1reportSingleClassLayoutC: 如/ d1reportSingleClassLayoutC报告的,这是类C的布局:

class C size(20):
        +---
        | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
        | +--- (base class B)
 8      | | {vfptr}
12      | | b
        | +---
16      | c
        +---

You are correct, there are two vtables, one for each base class. 没错,有两个vtable,每个基类一个。 This is how it works in multiple inheritance; 这就是它在多重继承中的工作方式。 if the C* is casted to a B*, the pointer value gets adjusted by 8 bytes. 如果将C *强制转换为B *,则指针值将调整8个字节。 A vtable still needs to be at offset 0 for virtual function calls to work. 为了使虚拟函数调用起作用,vtable仍需要位于偏移量0处。

The vtable in the above layout for class A is treated as class C's vtable (when called through a C*). 上面布局中A类的vtable被视为C类的vtable(通过C *调用时)。

The sizeof B is 16 bytes -------------- Without virtual it should be 4 + 4 + 4 = 12. why there is another 4 bytes here? B的大小是16个字节--------------如果没有虚拟字节,它应该是4 + 4 + 4 =12。为什么这里还有另外4个字节? What's the layout of class B ? B类的布局是什么?

This is the layout of class B in this example: 在此示例中,这是类B的布局:

class B size(20):
        +---
 0      | {vfptr}
 4      | {vbptr}
 8      | b
        +---
        +--- (virtual base A)
12      | {vfptr}
16      | a
        +---

As you can see, there is an extra pointer to handle virtual inheritance. 如您所见,还有一个额外的指针可以处理虚拟继承。 Virtual inheritance is complicated. 虚拟继承很复杂。

The sizeof D is 32 bytes -------------- it should be 16(class B) + 12(class C) + 4(int d) = 32. Is that right? D的大小是32个字节--------------它应该是16(B类)+ 12(C类)+4(int d)=32。对吗?

No, 36 bytes. 不,36个字节。 Same deal with the virtual inheritance. 同样处理虚拟继承。 Layout of D in this example: 本例中D的布局:

class D size(36):
        +---
        | +--- (base class B)
 0      | | {vfptr}
 4      | | {vbptr}
 8      | | b
        | +---
        | +--- (base class C)
        | | +--- (base class A)
12      | | | {vfptr}
16      | | | a
        | | +---
20      | | c
        | +---
24      | d
        +---
        +--- (virtual base A)
28      | {vfptr}
32      | a
        +---

My question is , why there is an extra space when virtual inheritance is applied? 我的问题是,为什么在应用虚拟继承时会有额外的空间?

Virtual base class pointer, it's complicated. 虚拟基类指针,很复杂。 Base classes are "combined" in virtual inheritance. 基类在虚拟继承中被“组合”。 Instead of having a base class embedded into a class, the class will have a pointer to the base class object in the layout. 该类将在布局中具有指向基类对象的指针,而不是将基类嵌入到类中。 If you have two base classes using virtual inheritance (the "diamond" class hierarchy), they will both point to the same virtual base class in the object, instead of having a separate copy of that base class. 如果您有两个使用虚拟继承的基类(“钻石”类层次结构),则它们都将指向对象中的同一虚拟基类,而不是具有该基类的单独副本。

What's the underneath rule for the object size in this case? 在这种情况下,对象大小的基本规则是什么?

Important point; 很重要的一点; there are no rules: the compiler can do whatever it needs to do. 没有规则:编译器可以执行所需的任何操作。

And a final detail; 最后的细节; to make all these class layout diagrams I am compiling with: 制作所有这些我正在编译的类布局图:

cl test.cpp /d1reportSingleClassLayoutXXX

Where XXX is a substring match of the structs/classes you want to see the layout of. 其中XXX是您要查看其布局的结构/类的子字符串匹配。 Using this you can explore the affects of various inheritance schemes yourself, as well as why/where padding is added, etc. 使用此功能,您可以自己探索各种继承方案的影响,以及为什么/在何处添加填充等。

A good way to think about it is to understand what has to be done to handle up-casts. 对此进行思考的一种好方法是了解处理向上转换必须执行的操作。 I'll try to answer your questions by showing the memory layout of objects of the classes you describe. 我将通过显示您描述的类的对象的内存布局来尝试回答您的问题。

Code sample #2 代码样本2

The memory layout is as follows: 内存布局如下:

vptr | A::a | B::b

Upcasting a pointer to B to type A will result in the same address, with the same vptr being used. 将指向B的指针上载到类型A将产生相同的地址,并使用相同的vptr。 This is why there's no need for additional vptr's here. 这就是为什么这里不需要其他vptr的原因。

Code sample #3 代码样本#3

vptr | A::a | vptr | B::b | C::c

As you can see, there are two vptr's here, just like you guessed. 如您所见,就像您猜到的那样,这里有两个vptr。 Why? 为什么? Because it's true that if we upcast from C to A we don't need to modify the address, and thus can use the same vptr. 因为的确,如果我们从C上载到A,就不需要修改地址,因此可以使用相同的vptr。 But if we upcast from C to B we do need that modification, and correspondingly we need a vptr at the start of the resulting object. 但是,如果我们从C转换为B,则确实需要进行修改,并且相应地,在结果对象的开始处需要vptr。

So, any inherited class beyond the first will require an additional vptr (unless that inherited class has no virtual methods, in which case it has no vptr). 因此,除了第一个继承的类之外,任何继承的类都将需要附加的vptr(除非继承的类没有虚拟方法,在这种情况下,它没有vptr)。

Code sample #4 and beyond 代码示例4及更高版本

When you derive virtually, you need a new pointer, called a base pointer , to point to the location in the memory layout of the derived classes. 当进行虚拟派生时,需要一个新的指针,称为基本指针 ,以指向派生类在内存布局中的位置。 There can be more than one base pointer, of course. 当然,可以有多个基本指针。

So how does the memory layout look? 那么内存布局如何? That depends on the compiler. 那取决于编译器。 In your compiler it's probably something like 在您的编译器中,可能类似于

vptr | base pointer | B::b | vptr | A::a | C::c | vptr | A::a
          \-----------------------------------------^

But other compilers may incorporate base pointers in the virtual table (by using offsets - that deserves another question). 但是其他编译器可能会在虚拟表中合并基本指针(通过使用偏移量-值得提出另一个问题)。

You need a base pointer because when you derive in a virtual fashion, the derived class will appear only once in the memory layout (it may appear additional times if it's also derived normally, as in your example), so all its children must point to the exact same location. 您需要一个基指针,因为当您以虚拟方式派生时,派生类将仅在内存布局中出现一次(如您的示例所示,如果它也是正常派生的,则可能会出现额外的次数),因此其所有子代都必须指向完全相同的位置。

EDIT: clarification - it all really depends on the compiler, the memory layout I showed can be different in different compilers. 编辑:澄清-这完全取决于编译器,我在不同的编译器中显示的内存布局可以不同。

Quote> My question is, what's the rule about the number of vptrs in inheritance? Quote>我的问题是,继承中vptrs的数量规则是什么?

There are no rulez, every compiler vendor is allowed to implement the semantics of inheritance the way he sees fit. 没有规则,每个编译器供应商都可以按照自己认为合适的方式实现继承的语义。

class B: public A {}, size = 12. That's pretty normal, one vtable for B that has both virtual methods, vtable pointer + 2*int = 12 B类:公共A {},大小=12。这很正常,B的一个vtable具有两个虚方法,vtable指针+ 2 * int = 12

class C : public A, public B {}, size = 20. C can arbitrarily extend the vtable of either A or B. 2*vtable pointer + 3*int = 20 C类:公共A,公共B {},大小=20。C可以任意扩展A或B的vtable。2 * vtable指针+ 3 * int = 20

Virtual inheritance: that's where you really hit the edges of undocumented behavior. 虚拟继承:这是您真正实现未记录行为的边缘的地方。 For example, in MSVC the #pragma vtordisp and /vd compile options become relevant. 例如,在MSVC中,#pragma vtordisp和/ vd编译选项变得相关。 There's some background info in this article . 有一些背景资料这篇文章 I studied this a few times and decided the compile option acronym was representative for what could happen to my code if I ever used it. 我对此进行了几次研究,并确定编译选项首字母缩写代表了如果我曾经使用过,我的代码可能会发生什么。

All of this is completely implementation defined you realize. 所有这些完全是您意识到的实现定义。 You can't count on any of it. 您不能指望任何它。 There is no 'rule'. 没有“规则”。

In the inheritance example, here is how the virtual table for classes A and B might look: 在继承示例中,这是类A和类B的虚拟表的外观:

      class A
+-----------------+
| pointer to A::v |
+-----------------+

      class B
+-----------------+
| pointer to A::v |
+-----------------+
| pointer to B::w |
+-----------------+

As you can see, if you have a pointer to class B's virtual table, it is also perfectly valid as class A's virtual table. 如您所见,如果您有一个指向类B的虚拟表的指针,那么它也完全可以作为类A的虚拟表使用。

In your class C example, if you think about it, there is no way to make a virtual table that is both valid as a table for class C, class A, and class B. So the compiler makes two. 在您的C类示例中,如果考虑一下,就没有办法创建一个虚拟表,该虚拟表对于C类,A类和B类都有效。 One virtual table is valid for class A and C (mostly likely) and the other is valid for class A and B. 一个虚拟表对A和C类有效(大多数情况下),而另一个对A和B类有效。

This obviously depends on the compiler implementation. 这显然取决于编译器的实现。 Anyway I think that I can sum up the following rules from the implementation given by a classic paper linked below and which gives the number of bytes you get in your examples (except for class D which would be 36 bytes and not 32!!!): 无论如何,我认为我可以从下面链接的经典论文给出的实现中总结出以下规则,该规则给出示例中获得的字节数(D类除外,该字节数为36字节而不是32字节!!!) :

The size of an object of class T is: T类的对象的大小为:

  • The size of its fields PLUS the sum of the size of every object from which T inherits PLUS 4 bytes for every object from which T virtually inherits PLUS 4 bytes ONLY IF T needs ANOTHER v-table 它的字段的大小加上T继承的每个对象的大小之和加上T虚拟继承的每个对象的4个字节,仅当T需要另一个v表时才加上4个字节
  • Pay attention: if a class K is virtually inherited multiple times (at any level) you have to add the size of K only once 请注意:如果一个类K实际上被多次继承(在任何级别),则只需将K的大小相加一次

So we have to answer another question: When does a class need ANOTHER v-table? 因此,我们必须回答另一个问题:什么时候班级需要另一个V表?

  • A class that does not inherit from other classes needs a v-table only if it has one or more virtual methods 不继承自其他类的类仅当具有一个或多个虚拟方法时才需要v表
  • OTHERWISE, a class needs another v-table ONLY IF NONE of the classes from which it non virtually inherits does have a v-table 否则,仅当一个非虚拟继承的类中没有一个具有v-table时,该类才需要另一个v-table

The End of the rules (which I think can be applied to match what Terry Mahaffey has explained in his answer) :) 规则的结尾(我认为可以用来匹配特里·马哈菲(Terry Mahaffey)在回答中解释的内容):)

Anyway my suggestion is to read the following paper by Bjarne Stroustrup (the creator of C++) which explains exactly these things: how many virtual tables are needed with virtual or non virtual inheritance... and why! 无论如何,我的建议是阅读Bjarne Stroustrup(C ++的创建者)的以下论文,该论文准确地解释了以下内容:使用虚拟或非虚拟继承需要多少个虚拟表...以及为什么!

It's really a good reading: http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html 确实是一本好书: http : //www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html

我不确定,但是我认为这是因为指向虚拟方法表的指针

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM