对C ++基类布局感到困惑

时间:2020-04-30 03:02:14

标签: c++ inheritance layout struct padding

这是我的代码

#include <bits/stdc++.h>


class A{
    int val;
    char c;
};
class B:public A{
    char val;
};

struct C{
    int val;
    char c;
};
struct D:public C{
    char val;
};


int main()
{
    std::cout<<sizeof(B)<<std::endl; //8
    std::cout<<sizeof(D)<<std::endl; //12

}

为什么classstruct的对齐方式不同


*** Dumping AST Record Layout
   0 | class A
   0 |   int val
   4 |   char c
     | [sizeof=8, dsize=5, align=4
     |  nvsize=5, nvalign=4]


*** Dumping AST Record Layout
   0 | class B
   0 |   class A (base)
   0 |     int val
   4 |     char c
   5 |   char val
     | [sizeof=8, dsize=6, align=4
     |  nvsize=6, nvalign=4]


*** Dumping AST Record Layout
   0 | struct C
   0 |   int val
   4 |   char c
     | [sizeof=8, dsize=8, align=4
     |  nvsize=8, nvalign=4]


*** Dumping AST Record Layout
   0 | struct D
   0 |   struct C (base)
   0 |     int val
   4 |     char c
   8 |   char val
     | [sizeof=12, dsize=9, align=4
     |  nvsize=9, nvalign=4]

2 个答案:

答案 0 :(得分:5)

struct情况下,请考虑以下程序:

void f(C& cx)
{
    cx.c = 'x';
}

int main()
{
    D d{};
    d.D::val = 'y';
    f(d);
    std::cout << d.D::val << '\n';
}

此代码必须输出y

在您的系统上,AC结构的大小为8,因为有一个成员大小为4且有一个char,并且该结构必须正确争取最大的成员。这些结构具有4个字节的int,1个字节的char和3个填充字节。

允许分配cx.c = 5;修改填充(任何结构分配都可以修改结构填充)。因此,填充不能用于存储基类元素。

然而,AB不可能有类似的例子,因为A的数据成员是私有的。函数void f(A& ax) { ax.c = 'x'; }不能存在,因此不会出现此问题,并且编译器可以避免使用A的填充区域来存储派生的类成员。


NB:由于在基类和派生类中都具有数据成员,因此这两个类都不是standard layout

答案 1 :(得分:1)

添加到@MM答案,看起来即使您具有类constructor的公共setterA成员函数,编译器仍会存储类B的数据成员在类A的填充区域中(我试图迫使编译器不要使用类A的尾部填充,但无法成功)。

class.mem/19中可以找到一条注释,上面写着:

[注意:分配了具有相同访问控制和非零大小([intro.object])的(非联盟)类的非静态数据成员,以便以后的成员在类对象中具有更高的地址。未指定具有不同访问控制的非静态数据成员的分配顺序。实现对齐要求可能会导致两个相邻成员不能立即彼此分配;管理虚拟功能([class.virtual])和虚拟基类([class.mi])的空间要求也可能如此。 —尾注]

this答案中添加更多内容:

该标准要求具有相同访问控制的成员在内存中分组在一起。该分组决定了如何填充对象,因此对其进行更改可以/将更改对象的大小。

还有this的更多答案:

这些类型的dsize,nvsize和nvalign被定义为它们的普通大小和对齐方式。这些属性仅对用作基类的非空类类型有效。我们忽略POD的尾部填充,因为该标准的早期版本不允许我们将其用于其他任何东西,并且因为它有时允许更快地复制该类型。

因此,在第一个示例中,A不是用于布局的POD,其尾部填充可用于B::val,但是在第二个示例中,它是{ {1}},并且其尾部填充无法重复使用。

POD

输出:

#include <iostream>


class A {
    int val;
    char c;
public:
    A(int a, char b): val(a), c(b)
    {

    }
public:
    void setC(int a)
    {
        c = a;
    }
    char getC(void) const
    {
        return c;
    }
};

class B: public A {
    char val;
public:
    B(void): A(1,'2'), val('2')
    {

    }
public:
    char getVal(void) const
    {
        return val;
    }
};

struct C {
    int val;
    char c;
};
struct D: public C {
    char val;
};


int main()
{
    B a;
    a.setC(2370);
    std::cout << a.getVal() << " & " << a.getC() << std::endl;
    std::cout << sizeof(B) << std::endl; // 8
    std::cout << sizeof(D) << std::endl; // 12
    return 0;
}

要了解有关课程的2 & B 8 12 的信息,请参见this