Обязательно ли макет объекта C++ обязательно статически определен?
Более конкретно, предполагая, A
является доступным базовым классом B
Приводит ли следующий код к неопределенному поведению, и гарантировано ли утверждение не срабатывать в соответствии со стандартом?
void test(B b1, B b2) {
A* a2 = &b2;
auto offset = reinterpret_cast<char*>(a2) - reinterpret_cast<char*>(&b2);
A* a1 = reinterpret_cast<A*>(reinterpret_cast<char*>(&b1) + offset);
assert(a1 == static_cast<A*>(&b1));
}
Редактировать: Мне известно, что все распространенные поставщики компиляторов реализуют макет объекта C++ (даже с учетом виртуального наследования) способом, который совместим с неявными предположениями о test
, То, что я ищу, является гарантией (явной или неявной) для такого поведения в стандарте. В качестве альтернативы, будет также принято достаточно подробное описание степени компоновки хранилища объектов, предоставляемой стандартом в качестве доказательства того, что такое поведение не гарантируется.
3 ответа
Если, например, тип стандартного макета, трудно понять, как реализация должна быть ограничена в этом смысле. Может ли реализация использовать какой-то динамический поиск для базового объекта, например? в теории, я думаю, да. (Опять же, на практике мне трудно понять, какая польза от смещения должна быть статичной и иметь дополнительные накладные расходы)
Например:
Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа (раздел 14) распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных с различным контролем доступа не определен (раздел 14). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (13.3) и виртуальными базовыми классами (13.1).
Стандарт ничего не гарантирует, например, в отношении виртуальных базовых классов.
Объект тривиально копируемого или стандартного типа (6.7) должен занимать непрерывные байты хранилища.
Опять же, это касается только подмножества, поэтому стандарт здесь не сильно помогает. (например, объект с виртуальной функцией нетривиален для копирования).
Также см., Что поставщик реализовал макрос смещения https://en.cppreference.com/w/cpp/types/offsetof
Хотя только для переменных-членов, даже здесь, это ясно показывает, что не так много всего, чтобы продолжить.
Как видите, большинство вещей оставлено на усмотрение реализации.
Также смотрите этот ответ (не тот же вопрос, но связанный): Стандарт C++ на адрес унаследованных членов
Нет, по причине, которая не имеет ничего общего с производными классами или reinterpret_cast: арифметика указателей не гарантирует, что вы вернете исходный ответ вне контекста массива. См. 5.7.4-5 (expr.add), в котором указывается, когда допустимо добавлять / вычитать указатели:
Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд-указатель указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующего и исходного элементов массива равна интегральному выражению.... Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, вычисление не должно приводить к переполнению; в противном случае поведение не определено.
Язык для вычитания немного более неоднозначен, но, по сути, говорит о том же.
Это может быть хорошо. При определенных условиях:
A
не является (частью) virtual
база, или b1
а также b2
иметь тот же самый производный тип, или вам (не) повезло.
Изменить: Ваше изменение от передачи по ссылке к передаче по значению делает тривиальным, чтобы показать выполнение условия выше.
Правила псевдонимов не будут мешать, так как используется только один неправильный тип char
и есть явное исключение для этого.