Класс, использующий виртуальное наследование, кажется, позволяет конструктору базового класса перезаписывать члены другого базового класса.
Я недостаточно знаком с макетом памяти объектов, которые содержат виртуальные базы, чтобы понять, почему следующее выглядит неправильно скомпилированным как 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)
байты и могут перезаписать их; однако это гарантировано для элементов массива и членов.