Запутался в макете базового класса c ++ - PullRequest
5 голосов
/ 30 апреля 2020

Вот мой код

#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

}

Почему class имеют другое выравнивание с struct


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

5 голосов
/ 30 апреля 2020

В случае 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.

В вашей системе структуры A и C имеют размер 8, поскольку существует элемент с размером 4 и символ, и структура должна быть правильно выровнена для самого большого элемента , Эти структуры имеют 4 байта типа int, 1 байт символа и 3 байта заполнения.

Назначение cx.c = 5; позволяет изменять заполнение (любое назначение структуры может изменять заполнение структуры). Поэтому это заполнение нельзя использовать для хранения элементов базового класса.

Однако аналогичный пример невозможен с A и B, поскольку члены данных A являются частными. Не может быть функции void f(A& ax) { ax.c = 'x'; }, поэтому эта проблема не возникает, и компилятор может избежать использования области дополнения A для хранения производных членов класса.


Примечание: ни один класс не является стандартная компоновка из-за наличия элементов данных в базовом и производном классах.

1 голос
/ 30 апреля 2020

Добавление к ответу @MM, похоже, даже если у вас есть функции-члены publi c constructor и setter для класса A, компилятор по-прежнему сохраняет члены-данные класса B в области заполнения класса A (я пытался заставить компилятор не использовать хвостовое заполнение класса A, но это не удалось).

Примечание можно найти в class.mem / 19 , говорящем:

[Примечание: Non-stati c членов данных (не Класс union) с тем же контролем доступа и ненулевым размером ([intro.object]) размещается так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения элементов данных без учета c с различным контролем доступа не определен. Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями ([class.virtual]) и виртуальными базовыми классами ([class.mi]). - конец примечания]

Добавление дополнительных от этого ответа:

Стандарт требует, чтобы элементы с одинаковым контролем доступа были сгруппированы в памяти. Эта группировка решает, как объект будет дополнен, поэтому изменение может изменить размер объекта.

И еще от этот ответ:

Dsize, nvsize и nvalign этих типов определены как их обычный размер и выравнивание. Эти свойства имеют значение только для непустых типов классов, которые используются в качестве базовых классов. Мы игнорируем хвостовое заполнение для POD, потому что ранняя версия стандарта не позволяла нам использовать его для чего-либо еще, и потому что иногда это позволяет быстрее копировать тип.

Таким образом, в вашем первом примере A не является POD для целей компоновки, и его хвостовое заполнение может использоваться для B::val, но во втором примере это 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 

Чтобы узнать о memory order and alignment для классов, см. this .

...