首页 文章

使用虚方法的C对象大小

提问于
浏览
24

我对虚拟对象大小有一些疑问 .

1) virtual function

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

A类的大小是8字节....一个整数(4个字节)加上一个虚拟指针(4个字节)很清楚!

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

B级的大小是多少?我使用sizeof B测试,它打印12

这是否意味着只有一个vptr,即使B类和A类都有虚函数?为什么只有一个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();
};

C的大小是20 ........

似乎在这种情况下,两个vptrs在布局中......这是怎么发生的?我认为两个vptrs一个用于A类,另一个用于B类....所以没有vptr用于C类的虚函数?

我的问题是,关于继承中vptrs数量的规则是什么?

2) virtual inheritance

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();
};

A的大小是8个字节-------------- 4(int a)4(vptr)= 8

B的大小是16个字节--------------没有虚拟它应该是4 4 4 = 12.为什么这里还有4个字节? B级的布局是什么?

C的大小是12个字节 . -------------- 4 4 4 = 12.很明显!

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是8

sizeof B是16

sizeof C是16

sizeof D是28这是指28 = 16(B类)16(C类) - 8(A类)4(这是什么?)

我的问题是,为什么在应用虚拟继承时会有额外的空间?

在这种情况下,对象大小的下面规则是什么?

虚拟应用于所有基类和部分基类时有什么区别?

6 回答

  • 0

    这是所有实现定义的 . 我正在使用VC10 Beta2 . 有助于理解这些东西的关键(虚函数的实现),您需要了解Visual Studio编译器中的秘密开关, /d1reportSingleClassLayoutXXX . 我会在一秒钟内完成 .

    基本规则是对于任何指向对象的指针,vtable需要位于偏移量0处 . 这意味着多个vtable用于多重继承 .

    在这里结合问题,我将从顶部开始:

    这是否意味着只有一个vptr,即使B类和A类都有虚函数?为什么只有一个vptr?

    这就是虚函数的工作方式,您希望基类和派生类共享相同的vtable指针(指向派生类中的实现) .

    在这种情况下,似乎有两个vptrs在布局中......这是怎么发生的?我认为两个vptrs一个用于A类,另一个用于B类....所以没有vptr用于C类的虚函数?

    这是C类的布局,由/ d1reportSingleClassLayoutC报告:

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

    你是对的,有两个vtable,每个基类一个 . 这是它在多重继承中的工作方式;如果C *被转换为B *,指针值将被调整8个字节 . vtable仍然需要在偏移0处才能使虚函数调用起作用 .

    上述A类布局中的vtable被视为C类的vtable(当通过C *调用时) .

    B的大小是16个字节--------------没有虚拟它应该是4 4 4 = 12.为什么这里还有4个字节? B级的布局是什么?

    这是此示例中B类的布局:

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

    如您所见,有一个额外的指针来处理虚拟继承 . 虚拟继承很复杂 .

    D的大小是32个字节--------------它应该是16(B类)12(C类)4(int d)= 32.是吗?

    不,36个字节 . 同样处理虚拟继承 . 本例中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
            +---
    

    我的问题是,为什么在应用虚拟继承时会有额外的空间?

    虚基类指针,它很复杂 . 基类在虚拟继承中“组合” . 该类不是将基类嵌入到类中,而是具有指向布局中基类对象的指针 . 如果您有两个使用虚拟继承的基类(“菱形”类层次结构),它们将指向对象中的相同虚拟基类,而不是具有该基类的单独副本 .

    在这种情况下,对象大小的下面规则是什么?

    很重要的一点;没有规则:编译器可以做任何需要做的事情 .

    最后的细节;制作我正在编译的所有这些类布局图:

    cl test.cpp /d1reportSingleClassLayoutXXX
    

    其中XXX是结构/类的子字符串匹配,您希望看到它的布局 . 使用此方法,您可以自己探索各种继承方案的影响,以及添加填充的原因/位置,等等

  • 2

    引用>我的问题是,关于继承中的vptrs数量的规则是什么?

    没有规则,允许每个编译器供应商以他认为合适的方式实现继承的语义 .

    B级:公共A {},大小= 12.这很正常,B的一个vtable有两个虚方法,vtable指针2 * int = 12

    C类:公共A,公共B {},大小= 20.C可以任意扩展A或B的vtable . 2 * vtable指针3 * int = 20

    虚拟继承:this article中的's where you really hit the edges of undocumented behavior. For example, in MSVC the #pragma vtordisp and /vd compile options become relevant. There' s一些背景信息 . 我研究了几次,并决定编译选项的首字母缩写词代表我的代码可能发生的事情,如果我曾经使用它 .

  • 3

    思考它的一个好方法是了解必须采取哪些措施来处理上转 . 我将尝试通过显示您描述的类的对象的内存布局来回答您的问题 .

    代码示例#2

    内存布局如下:

    vptr | A::a | B::b
    

    将指向B的指针向上转换为类型A将导致相同的地址,并使用相同的vptr . 这就是为什么这里不需要额外的vptr .

    代码示例#3

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

    如您所见,有两个vptr 's here, just like you guessed. Why? Because it'是真的,如果我们从C转发到A,我们不需要修改地址,因此可以使用相同的vptr . 但是如果我们从C转发到B,我们需要修改,相应地我们需要在生成的对象的开头有一个vptr .

    因此,除了第一个之外的任何继承类都需要额外的vptr(除非继承的类没有虚方法,在这种情况下它没有vptr) .

    代码示例#4及更高版本

    虚拟派生时,需要一个名为 base pointer 的新指针来指向派生类的内存布局中的位置 . 当然,可以有多个基指针 .

    那么内存布局看起来如何?这取决于编译器 . 在你的编译器中它可能就像

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

    但是其他编译器可能会在虚拟表中包含基本指针(通过使用偏移量 - 这值得另一个问题) .

    你需要一个基本指针,因为当你以虚拟方式派生时,派生类只会在内存布局中出现一次(如果它也正常派生,可能会出现额外的次数,如你的例子),所以它的所有子节点都必须指向完全相同的位置 .

    编辑:澄清 - 这一切都取决于编译器,我展示的内存布局可能在不同的编译器中有所不同 .

  • 1

    所有这些都是您实现的完全实现定义 . 你不能指望任何一个 . 没有“规则” .

    在继承示例中,以下是类A和B的虚拟表的外观:

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

    如您所见,如果您有一个指向B类虚拟表的指针,它也完全有效,因为它是A类的虚拟表 .

    在你的C类例子中,如果你考虑一下,就没有办法让一个虚拟表同时作为C类,A类和B类的表有效 . 所以编译器会生成两个 . 一个虚拟表对A类和C类(大部分可能)有效,另一个对A类和B类有效 .

  • 3

    这显然取决于编译器实现 . 无论如何,我认为我可以从下面链接的经典论文给出的实现中总结以下规则,并给出你在示例中得到的字节数(除了D类,它是36字节而不是32 !!!) :

    T类对象的大小是:

    • 其字段的大小加上T为每个对象继承PLUS 4个字节的每个对象的大小之和,如果T需要另外的v-table,则T虚拟继承PLUS 4个字节

    • 注意:如果类K实际上是多次遗传(在任何级别),你只需要添加一次K的大小

    所以我们必须回答另一个问题:一个类什么时候需要另一个v-table?

    • 只有具有一个或多个虚方法的类不从其他类继承才需要v表

    • OTHERWISE,一个类只需要另一个v-table,如果它实际上没有继承的类没有v-table

    规则的结束(我认为可以应用于匹配Terry Mahaffey在答案中解释的内容):)

    无论如何,我的建议是阅读Bjarne Stroustrup(C的创建者)的以下论文,它解释了这些事情:虚拟或非虚拟继承需要多少个虚拟表......以及为什么!

    这真是一个很好的阅读:http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html

  • 21

    我不确定,但我认为这是因为指针Virtual method table

相关问题