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

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

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

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

1 Ответ

0 голосов
/ 09 января 2019

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

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

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

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

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

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

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

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

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

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

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

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

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

Резюме:

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

...