Класс, использующий виртуальное наследование, кажется, позволяет конструктору базового класса перезаписывать члены другого базового класса.

Я недостаточно знаком с макетом памяти объектов, которые содержат виртуальные базы, чтобы понять, почему следующее выглядит неправильно скомпилированным как clang, так и gcc. Это академическое упражнение, поэтому, пожалуйста, извините за легкомыслие memset() в конструкторе. Я тестирую с использованием Linux x86-64 с clang 7 и gcc 8.2:

#include <cstring>

struct A {
    A() { memset(this, 0, sizeof(A)); }

    int i;
    char a;
};

struct B { char b = 'b'; };
struct C : virtual B, A {};

char foo() {
    C c;
    return c.b;
}

Когда скомпилировано с -O2 -Wall -pedantic -std=c++17оба компилятора выдают следующую сборку без предупреждений:

foo():
    xor     eax, eax
    ret

изменения C не наследовать B виртуально или меняется sizeof(A) в 5 или менее в вызове к memset оба изменяют вывод компиляторов, чтобы вернуть 'b', как я и ожидал:

foo():
    mov     al, 98     # gcc uses eax directly, here
    ret

Что такое макет памяти C когда это происходит от B практически / не виртуально, и являются ли эти компиляторы ошибочными, позволяя Aконструктор для обнуления членов другого базового класса? Я знаю, что макет не определен стандартом, но я ожидаю, что все реализации гарантируют, что конструктор класса не может вмешиваться в члены данных несвязанного класса, даже если используется в множественном наследовании, подобном этому. Или хотя бы предупредить, что что-то подобное может произойти. (новый GCC -Wclass-memaccess предупреждение не диагностируется здесь).

Если это сводится к memset(this, 0, sizeof(A)) Будучи недопустимым в конструкторе, я бы ожидал, что компиляторы либо не смогут скомпилироваться, либо хотя бы предупредят.

Ссылка: https://godbolt.org/z/OSQV1j

1 ответ

Я недостаточно знаком с макетом памяти объектов, которые содержат виртуальные базы

Виртуальные базы (и подобъекты базового класса, имеющие виртуальные основания) в общем случае не создаются и не представляются как полные объекты тех же типов, в отличие от любого другого подобъекта, имеющего такую ​​же компоновку (относительная позиция каждого подобъекта подобъекта базового класса).) как законченный объект того же типа:

  • элемент массива
  • ученик
  • подобъект не виртуального базового класса, не имеющий виртуального базового класса

которые все построены и представлены как законченный объект.

Пояснение: хотя конструктор для подобъекта базового класса обычно такой же, как и для полного объекта, память, зарезервированная исключительно для подобъекта базового класса, может быть меньше его обычного размера.

чтобы понять, почему следующее выглядит неправильно скомпилированным как clang, так и gcc.

Вы не опубликовали никаких доказательств плохой генерации кода.

Это академическое упражнение, поэтому, пожалуйста, извините за легкомыслие

Это не легкомысленно, это явно неправильно.

memset() в конструкторе копирования.

Это разрушает объект, перезаписывая его.

В коде используется неподдерживаемая операция (перезаписывается память c2 объект во время создания), и компилятор не предупреждает вас, что ваш код использует объект, время жизни которого было завершено вызовом функции доступа к памяти низкого уровня (memset). Завершение времени жизни внутри конструктора базового класса недопустимо: технически время жизни даже не начинается, когда вы его заканчиваете.

Если вы хотите закончить время жизни объекта, перезаписав его, сделайте это после построения.

Резюме:

Нет гарантии, что каждый подобъект типа T "своя" sizeof (T) байты и могут перезаписать их; однако это гарантировано для элементов массива и членов.

Другие вопросы по тегам