对 C++ 基础 class 布局感到困惑

Confused about c++ base class layout

这是我的代码

#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]

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 和一个字符,并且该结构必须与其最大的成员正确对齐。这些结构有 4 个字节的 int、1 个字节的 char 和 3 个填充字节。

赋值cx.c = 5;允许修改padding(任何struct赋值都可以修改struct padding)。因此,填充不能用于存储基础 class 元素。

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


注意:class 都不是 standard layout,因为在基础和派生的 classes 中都有数据成员。

添加到@M.M 答案,看起来即使你有 public constructorsetter 成员函数 class A ,编译器仍然将 class B 数据成员存储在 class A 的填充区域(我试图强制编译器不要使用 [=45 的尾部填充=] A 但未能成功)。

可以在 class.mem/19 中找到注释说:

[ Note: Non-static data members of a (non-union) class with the same access control and non-zero size ([intro.object]) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions ([class.virtual]) and virtual base classes ([class.mi]). — end note ]

添加来自 this 答案的更多内容:

The standard requires members with the same access control to be grouped together in memory. That grouping decides how the object gets padded so changing it can/will change the size of the object.

更多来自 this 答案:

The dsize, nvsize, and nvalign of these types are defined to be their ordinary size and alignment. These properties only matter for non-empty class types that are used as base classes. We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.

因此,在您的第一个示例中,A 不是出于布局目的的 POD,其尾部填充可用于 B::val,但在您的第二个示例中,它是a 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 

要了解 classes 的 memory order and alignment,请参阅 this