При расширении проложенной структуры, почему дополнительные поля не могут быть помещены в дополнение к хвосту?
Рассмотрим структуры:
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 без нарушения ограничений выравнивания. Каково правило, которое его предотвращает, и в чем причина этого?
Ответы
Ответ 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 выровнен. Когда доступ к памяти не выровнен, он считается несогласованным.