Подтвердить что ты не робот

Зачем нужен трюк для этой структуры?

Рассмотрим эту простую программу

#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 со следующими компиляторами:

  • clang 3.6
  • gcc 5.1

Я также заметил что-то странное из clag struct dumper. Если мы попросим размер данных без заполнения хвоста ( "dsize" ),

          A   B
first     8   9
second    7   8

то в первом примере получим dsize(A) == 8. Почему это не 7?

4b9b3361

Ответ 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!