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

При расширении проложенной структуры, почему дополнительные поля не могут быть помещены в дополнение к хвосту?

Рассмотрим структуры:

struct S1 {
    int a;
    char b;
};

struct S2 {
    struct S1 s;       /* struct needed to make this compile as C without typedef */
    char c;
};

// For the C++ fans
struct S3 : S1 {
    char c;
};

Размер S1 равен 8, что ожидается из-за выравнивания. Но размер S2 и S3 равен 12. Это означает, что компилятор их структурирует как:

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11|
|       a       | b |  padding  | c |  padding  |

Компилятор может разместить c в дополнении в 6 7 8 без нарушения ограничений выравнивания. Каково правило, которое его предотвращает, и в чем причина этого?

4b9b3361

Ответ 1

Короткий ответ (для части вопроса С++) : Itanium ABI для С++ по историческим причинам запрещает использование хвостового дополнения базового подобъекта типа POD. Обратите внимание, что С++ 11 не имеет такого запрета. Соответствующее правило 3.9/2, которое позволяет копировать типы с возможностью копирования с помощью их основного представления, явно исключает базовые подобъекты.


Длинный ответ: Я попытаюсь обработать С++ 11 и C сразу.

  • Макет S1 должен содержать отступы, так как S1::a должен быть выровнен для int, а массив S1[N] состоит из смежно выделенных объектов типа S1, каждый из членов a должен быть выровненными.
  • В С++ объекты тривиально-скопируемого типа T, которые не являются базовыми подобъектами, могут рассматриваться как массивы sizeof(T) байтов (т.е. вы можете наложить указатель на объект unsigned char * и обработать результат как указатель на первый элемент a unsigned char[sizeof(T)], и значение этого массива определяет объект). Так как все объекты в C такого типа, это объясняет S2 для C и С++.
  • Интересными случаями, остающимися для С++, являются:
    • базовые подобъекты, которые не подпадают под действие вышеуказанного правила (см. С++ 11 3.9/2) и
    • любой объект, который не имеет тривиально-скопируемого типа.

В 3.1 существует действительно распространенная, популярная "оптимизация базовой компоновки", в которой компиляторы "сжимают" элементы данных класса в базовые подобъекты. Это наиболее поразительно, когда базовый класс пуст (& infin;% уменьшение размера!), Но применяется в более общем плане. Тем не менее, Itanium ABI для С++, который я связал выше и который многие компиляторы реализуют, запрещает такое сжатие хвостового покрытия, когда соответствующий базовый тип - POD (и POD - тривиально-копируемый и стандартный макет).

Для 3.2 применяется одна и та же часть приложения Itanium ABI, хотя в настоящее время я не считаю, что стандарт С++ 11 фактически предусматривает, что произвольные, не тривиально-подлежащие копированию объекты-члены должны иметь тот же размер, что и полный объект тот же тип.


Предыдущий ответ сохранен для справки.

Я считаю, что это потому, что S1 является стандартным макетом, поэтому по какой-то причине S1 -подбор объекта S3 остается нетронутым. Я не уверен, что это предусмотрено стандартом.

Однако, если мы превратим S1 в нестандартную компоновку, мы наблюдаем оптимизацию компоновки:

struct EB { };

struct S1 : EB {   // not standard-layout
    EB eb;
    int a;
    char b;
};

struct S3 : S1 {
    char c;
};

Теперь sizeof(S1) == sizeof(S3) == 12 на моей платформе. Живая демонстрация.

И вот более простой пример:

struct S1 {
private:
    int a;
public:
    char b;
};

struct S3 : S1 {
    char c;
};

Смешанный доступ делает S1 нестандартную макет. (Теперь sizeof(S1) == sizeof(S3) == 8.)

Обновление:. Определяющим фактором является тривиальность, а также стандартная макет, т.е. класс должен быть POD. Следующий класс стандартного макета не-POD оптимизирован для базового макета:

struct S1 {
    ~S1(){}
    int a;
    char b;
};

struct S3 : S1 {
    char c;
};

Снова sizeof(S1) == sizeof(S3) == 8. Демо

Ответ 2

Рассмотрим некоторый код:

struct S1 {
    int a;
    char b;
};

struct S2 {
    S1 s;
    char c;
};

Рассмотрим, что произойдет, если sizeof(S1) == 8 и sizeof(S2) == 8.

struct S2 s2;
struct S1 *s1 = &(s2.s);
memset(s1, 0, sizeof(*s1));

Теперь вы перезаписали S2::c.


Для причин выравнивания массива S2 также не может иметь размер 9, 10 или 11. Таким образом, следующий допустимый размер равен 12.

Ответ 3

Вот несколько примеров, почему компилятор не может разместить элемент c в концевой части элемента struct S1 s. Предположим, что компилятор разместил struct S2.c в дополнении члена struct S1.s.:

struct S1 {
    int a;
    char b;
};

struct S2 {
    struct S1 s;       /* struct needed to make this compile as C without typedef */
    char c;
};

// ...

struct S1 foo = { 10, 'a' };
struct S2 bar = {{ 20, 'b'}, 'c' };

bar.s = foo;    // this will likely corrupt bar.c

memcpy(&bar.s, &foo, sizeof(bar.s));    // this will certainly corrupt bar.c

bar.s.b = 'z';  // this is permited to corrupt bar by C99 6.2.6.1/6

C99/C11 6.2.6.1/6 ( "Представление типов/общее" ) говорит:

Когда значение хранится в объекте структуры или типа объединения, в том числе в объекте-члене, байты представления объекта которые соответствуют любым байтам заполнения, принимают неопределенные значения.

Ответ 4

В чем причина дополнительного дополнения в структурах?

Если процессор серьезно относится к выравниванию, он генерирует исключение/сигнал, в противном случае будет наблюдаться снижение производительности за счет снижения скорости передачи данных.

Чтобы понять это, начните с выравнивания структуры данных:

Выравнивание структуры данных - это способ организации и доступа к данным в памяти компьютера. Он состоит из двух отдельных, но связанных с ними проблем: выравнивание данных и добавление структуры данных. Когда современный компьютер считывает или записывает на адрес памяти, он будет делать это в кусках размера слова (например, 4 байтовых блока в 32-битной системе) или больше. Согласование данных означает перенос данных со смещением памяти, равным нескольким кратным размеру слова, что увеличивает производительность системы из-за того, как процессор обрабатывает память. Чтобы выровнять данные, может потребоваться вставить некоторые бессмысленные байты между концом последней структуры данных и началом следующего, который является дополнением к структуре данных.

Например, когда размер компьютерного слова составляет 4 байта (байт означает 8 бит на большинстве машин, но может отличаться в некоторых системах), данные, которые нужно читать, должны иметь смещение памяти, которое несколько кратно 4. Если это не так, например данные начинаются с 14 -го байта вместо 16 -го байта, тогда компьютер должен прочитать два 4 байтовых блока и выполнить некоторые вычисления до того, как запрошенные данные будут прочитаны, или может вызвать ошибку выравнивания. Несмотря на то, что предыдущая структура данных заканчивается на 13-м байте, следующая структура данных должна начинаться с 16-го байта. Два двоичных байта вставлены между двумя структурами данных, чтобы выровнять следующую структуру данных с 16-м байтом.


При расширении проложенной структуры, почему дополнительные поля не могут быть помещены в хвостовое дополнение?

Компилятор может разместить c в дополнении в 6 7 8 без нарушения ограничений выравнивания. Каково правило, которое его предотвращает, и в чем причина этого?

Компилятор может разместить его там, но тогда доступ к памяти для c будет искажать 1 и будет наказание за производительность, как описано выше. Чтобы добавить массив:

struct __attribute__((__packed__)) mypackedstruct{
    char a;
    int b;
    char c;
};  

Эта структура будет иметь скомпилированный размер 6 байтов в 32-битной системе.
Недопустимый доступ к памяти медленнее на архитектурах, которые позволяют это (например, x86 и amd64), и явно запрещены для строгих архитектур выравнивания, таких как SPARC.


1 Говорят, что доступ к памяти выравнивается, когда доступ к данным, имеет длину n байтов (где n - мощность 2), а нулевой адрес n -byte выровнен. Когда доступ к памяти не выровнен, он считается несогласованным.