В следующем коде оба D1
и D2
имеют одни и те же элементы данных. Однако размер объекта различается в зависимости от того, инициализирован ли последний член базового класса.
#include <cstdint>
struct B1 { int x; short y = 0; };
struct D1 : B1 { short z; };
struct B2 { int x; short y; };
struct D2 : B2 { short z; };
static_assert(sizeof(D1) == sizeof(D2));
GCC 14.2 (и Clang 18.0.1) терпит неудачу:
<source>:7:26: error: static assertion failed
7 | static_assert(sizeof(D1) == sizeof(D2));
| ~~~~~~~~~~~^~~~~~~~~~~~~
<source>:7:26: note: the comparison reduces to '(8 == 12)'
Каково объяснение такого поведения?
Очевидно, D1
и D2
имелись в виду, отредактированные.
Макет классов, используемый GCC и Clang (в обычном режиме), определяется ABI Itanium C++: itanium-cxx-abi.github.io/cxx-abi/abi.html#class-types Однако он довольно плотный. , и я не пробираюсь через это. Их ключевое отличие, вероятно, заключается в том, что B2 является POD, а B1 — нет.
static_assert
проходит, если все в B2
отмечено private
. Это забавная головоломка.
Любопытный! B2 — стандартная и тривиальная компоновка. B1 — стандартная компоновка и нетривиальная. D2 имеет нестандартную компоновку и тривиален. D1 — нестандартная компоновка и нетривиальная.
Это произвольный выбор ABI для совместимости с C.
В ABI Itanium C++, реализованном здесь с помощью GCC и Clang, B2
считается POD для целей макета, а B1
— нет. (Это разумно, поскольку B2
также является допустимым типом C, а B1
— нет.)
ABI указывает, что заполнение хвоста типа будет повторно использоваться только в том случае, если он не является POD для целей макета. (Так, например, memcpy
/memset
, как правило, используется в C, безопасно для подобъекта базового класса, если тип базового класса - POD для целей макета и не перезаписывает какие-либо члены производного класса. А в C компилятору всегда разрешено перезаписывать заполнение, если член изменен, поэтому даже без вызовов memcpy
и т. д. использование подобъекта базового класса в C в противном случае было бы несовместимо с C++.)
Оба B1
и B2
имеют двухбайтовое заполнение хвоста. D1
будет повторно использовать его, чтобы он соответствовал члену z
, а D2
не будет и вместо этого оставит два байта заполнения пустыми. Поскольку размер всего класса должен быть кратен его выравниванию, что 4
из-за члена int
, D2
тогда также необходимо добавить два дополнительных новых байта заполнения хвоста.
Или, если быть более точным: мне кажется, что Itanium ABI четко не указывает, делает ли инициализатор элемента по умолчанию тип не POD для целей макета. Он использует определение POD в C++03, которое, очевидно, не знает об инициализаторах членов по умолчанию, которые были представлены в C++11. Однако кажется, что GCC и Clang, по крайней мере, согласились, что инициализатор члена по умолчанию должен сделать класс не POD для целей макета, и в целом имеет смысл, что более поздним расширениям языка C++ не нужно сильно беспокоиться о совместимости с C. (Можно спорить в любом направлении, но, по крайней мере, определение с инициализатором элемента по умолчанию не может использоваться в C без изменений.)
Я не уверен, почему это меняет ситуацию. Ни D1, ни D2 не являются допустимыми структурами C, так не может ли он все равно повторно использовать заполнение? Или B2, являющийся POD, означает «вы не должны повторно использовать дополнение ни при каких обстоятельствах»?
@Yksisarvinen С помощью этой спецификации вы можете безопасно создать объект D2
и передать его как B2*
в функцию C, которая может перезаписать его заполнение. (Кстати, не только через memcpy
/memset
. В C компилятору всегда разрешено перезаписывать дополнение при любой модификации члена.) Но для D1
/B1
это не проблема, потому что определение B1
невозможно даже проанализировать в C. .
@Yksisarvinen, но B2 является допустимой структурой C. Если sizeof(D2) == 8, то memcpy/memset в B2 перезапишет члены производного класса, даже если сам D2 нельзя будет использовать в коде C.
@MarkB • Члены D2 не «упакованы» в заполнение B2. Если процедура C использует memset больше, чем sizeof(B2)
, значит, она делает что-то нехорошее.
«D2 и B2 имеют одни и те же элементы данных». Это не так. Вы имеете в виду
D1
иD2
?