Зачем нужен трюк для этой структуры?
Рассмотрим эту простую программу
#include <iostream>
struct A
{
int x1234;
short x56;
char x7;
};
struct B : A
{
char x8;
};
int main()
{
std::cout << sizeof(A) << ' ' << sizeof(B) << '\n';
return 0;
}
Отпечатает 8 12
. Даже если B
может быть упакован в 8 байтов без нарушения требований к выравниванию, вместо этого он принимает жадные 12 байт.
Было бы неплохо иметь sizeof(B) == 8
, но ответ на
Является ли размер структуры, который должен быть точным кратным выравниванию этой структуры? предполагает, что нет способа.
Поэтому я был удивлен, когда следующий
struct MakePackable
{
};
struct A : MakePackable
{
int x1234;
short x56;
char x7;
};
struct B : A
{
char x8;
};
напечатано 8 8
.
Что здесь происходит? Я подозреваю, что стандартные макеты-типы имеют к этому какое-то отношение. Если да, то в чем причина его возникновения, поскольку единственная цель этой функции - обеспечить двоичную совместимость с C?
EDIT: Как указывали другие, это ABI или специфический для компилятора, поэтому я должен добавить, что это поведение наблюдалось на x86_64-unknown-linux-gnu со следующими компиляторами:
Я также заметил что-то странное из clag struct dumper. Если мы попросим размер данных без заполнения хвоста ( "dsize" ),
A B
first 8 9
second 7 8
то в первом примере получим dsize(A) == 8
. Почему это не 7?
Ответы
Ответ 1
Я не настоящий юрист языка С++, однако то, что я нашел до сих пор:
Ссылаясь на ответы в на этот вопрос, структура остается только стандартным макетом POD, в то время как существует только один класс с нестационарными членами между собой и его родительскими классами. Поэтому при этой идее A
имеет гарантированный макет в обоих случаях, но B
в обоих случаях не работает.
Поддержка этого факта заключается в том, что std:: is_pod имеет значение true для A
и false для B
в обоих.
Итак, если я правильно понимаю это, компилятору разрешено делать то, что он хочет, с макетом B
в обоих случаях. И, по-видимому, во втором случае кажется, что использовать то, что в противном случае было бы байтом заполнения A
.
Ответ 2
Это точка данных, хотя это не полный ответ.
Скажем, что у нас (как полная единица перевода, а не фрагмент):
struct X {};
struct A
{
int x1234;
short x56;
char x7;
}
void func(A &dst, A const &src)
{
dst = src;
}
С g++ эта функция скомпилируется с помощью:
movq (%rdx), %rax
movq %rax, (%rcx)
Однако, если вместо struct A : X
используется эта функция:
movl (%rdx), %eax
movl %eax, (%rcx)
movzwl 4(%rdx), %eax
movw %ax, 4(%rcx)
movzbl 6(%rdx), %eax
movb %al, 6(%rcx)
Эти два случая фактически соответствуют размерам 8 12
и 8 8
соответственно в примере OP.
Причина этого довольно ясна: A
может использоваться как основа для некоторого класса B
, а затем вызов func(b, a);
должен быть осторожным, чтобы не беспокоить других членов B
, которые могут находиться в область заполнения (b.x8
в примере OP);
Я не вижу никакого частного свойства A : X
в стандарте С++, который заставил бы g++ решить, что дополнение будет повторно использоваться в struct A : X
, но не в struct A
. Оба A
и A : X
являются тривиально скопируемыми, стандартными макетами и POD.
Я предполагаю, что это просто решение по оптимизации, основанное на типичном использовании. Версия без повторного использования будет быстрее скопировать. Может быть, разработчик g++ ABI мог бы комментировать?
Интересно, что этот пример показывает, что, будучи тривиально скопируемым, не означает, что memcpy(&b, &a, sizeof b)
эквивалентно b = a
!