Более конкретно, предполагая, 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
, То, что я ищу, является гарантией (явной или неявной) для такого поведения в стандарте. В качестве альтернативы, будет также принято достаточно подробное описание степени компоновки хранилища объектов, предоставляемой стандартом в качестве доказательства того, что такое поведение не гарантируется.
Это может быть хорошо. При определенных условиях:
A
не является (частью) virtual
база, или b1
а также b2
иметь тот же самый производный тип, или вам (не) повезло.
Редактировать: Ваш переход от передачи по ссылке к передаче по значению упрощает показ условия выше.
Правила псевдонимов не будут мешать как единственный неправильно используемый тип char
и есть явное исключение для этого.
Если, например, тип стандартного макета, трудно понять, как реализация должна быть ограничена в этом смысле. Может ли реализация использовать какой-то динамический поиск для базового объекта, например? в теории, я думаю, да. (Опять же, на практике мне трудно понять, какая польза от смещения должна быть статичной и иметь дополнительные накладные расходы)
Например:
Нестатические члены данных (не объединяющие) класс с одинаковым доступом
контроль (пункт 14) распределяются так, чтобы у более поздних членов
адреса внутри объекта класса. Порядок выделения нестатических
Элементы данных с разным контролем доступа не определены (п. 14).
Требования выравнивания реализации могут привести к двум соседним элементам
не распределяются сразу после друг друга; так может
требования к пространству для управления виртуальными функциями (13.3) и
виртуальные базовые классы (13.1).
Стандарт ничего не гарантирует, например, в отношении виртуальных базовых классов.
Объект тривиально копируемого или стандартного типа (6.7) должен
занимают смежные байты памяти.
Опять же, это касается только подмножества, поэтому стандарт здесь не сильно помогает. (например, объект с виртуальной функцией нетривиален для копирования).
Также смотрите, как вендор реализовал макрос offsetof https://en.cppreference.com/w/cpp/types/offsetof
Хотя только для переменных-членов, даже здесь, это ясно показывает, что не так много всего, чтобы продолжить.
Как видите, большинство вещей оставлено на усмотрение реализации.
Также посмотрите этот ответ (не тот же вопрос, но связанный): Стандарт C ++ на адрес унаследованных членов
Нет, по причине, которая не имеет ничего общего с производными классами или reinterpret_cast: арифметика указателей не гарантирует, что вы вернете исходный ответ вне контекста массива. Увидеть 5.7.4-5 (expr.add), которые указывают, когда можно добавлять / вычитать указатели:
Когда выражение с целочисленным типом добавляется или вычитается из указателя, результат имеет тип
операнда указателя. Если операнд-указатель указывает на элемент объекта массива, а массив
достаточно большой, результат указывает на смещение элемента от исходного элемента, так что разница
индексы результирующих и исходных элементов массива равны интегральному выражению. … Если и операнд указателя, и результат указывают на элементы одного и того же объекта массива или одного последнего
последний элемент объекта массива, оценка не должна производить переполнение; в противном случае поведение
не определено.
Язык для вычитания немного более неоднозначен, но, по сути, говорит о том же.