Из C23 §6.7.2.1(20):
[...] когда оператор
.
(или->
) имеет левый операнд, который является (указателем) на структуру с гибким элементом массива, а правый операнд называет этот элемент, он ведет себя так, как если бы этот член был заменен самым длинным массив (с тем же типом элемента), который не делает структуру больше объекта, к которому осуществляется доступ; смещение массива должно оставаться смещением гибкого элемента массива, даже если оно будет отличаться от смещения заменяющего массива.
Я не уверен, что понимаю последнее предложение. Почему смещение замещающего массива отличается от смещения гибкого элемента массива (FAM)? Если заменяющий массив имеет тот же тип элемента, что и FAM, то не будет ли он иметь то же выравнивание, что и FAM, и, следовательно, то же самое смещение, что и FAM?
Спасибо @EricPostpischil, я думал, что это будет следовать из требования, чтобы замещающий массив имел тот же тип элемента, что и FAM. Я до сих пор не понимаю, почему это не так.
Хотя мне ничего не известно, реализация C может решить сделать последний член структуры «выровненным по правому краю». Например, при четырехбайтовом выравнивании int
и восьмибайтовом выравнивании double
реализация C может составить struct { double d; int i[5]; }
восемь байтов d
, четыре байта заполнения, затем 20 байтов i
, тогда как struct {double d; int i[6]; }
будет восемь байтов d
, без байтов заполнения, тогда 24 байта i
. Оба они выравнивают свои элементы по мере необходимости и используют тот же объем пространства, что и традиционный макет (минимальное необходимое дополнение перед каждым элементом, а затем то, что необходимо в конце)…
… Итак, если они оба используют одно и то же пространство и удовлетворяют всем требованиям, зачем предпочитать традиционную планировку макету с выравниванием по правому краю? Другая гипотетическая возможность заключается в том, что реализация C может использовать другое выравнивание для больших членов структуры. Например, если член равен int i[1024];
, то выровняйте его по строке кэша, а не только по четырем байтам, как это было бы сделано для int i[3];
. Тогда для учета этого необходимо правило гибких смещений элементов массива.
@EricPostpischil: ваши аргументы звучат правильно, но правильное выравнивание члена гибкого массива выглядит странно ;-)
Спасибо @EricPostpischil, мне нравится пример с кешем.
Стандарт C не описывает подробно, как заполнение располагается в структуре. Единственное требование — отсутствие отступов в начале.
В частности, раздел 6.7.2.1p17 стандарта C23 гласит:
... Внутри объекта структуры могут быть безымянные отступы, но не в его начале
И 6.7.2.1p19 гласит:
В конце структуры или объединения может быть безымянное дополнение.
Например, учитывая следующие структуры:
struct foo1 {
int i;
double d;
int f[3];
};
struct foo2 {
int i;
double d;
int f[];
};
Компилятор мог бы расположить их следующим образом:
// total size: 32 bytes
struct foo1 {
int i; // offset 0
// 4 bytes padding
double d; // offset 8
// 4 bytes padding
int f[3]; // offset 20
};
// total size: 16 bytes + flexible array member
struct foo2 {
int i; // offset 0
// 4 bytes padding
double d; // offset 8
int f[]; // offset 16
};
Тогда, если вы создали экземпляр struct foo2
с тремя гибкими членами массива следующим образом:
struct foo2 *x = malloc(sizeof(struct foo2) + 3 * sizeof(int));
«Масив замены» в этом случае будет иметь тип int[3]
, поскольку это максимально допустимый размер с учетом фактического выделенного объекта.
Смещение x->f
должно быть равно 16 в соответствии с отрывком, который вы выделили жирным шрифтом, даже если экземпляр struct foo1
, член f
которого имеет тот же размер, что и массив замены выше, будет иметь этот элемент со смещением 20.
Спасибо @dbush, но почему смещение foo1.f
должно иметь какое-либо отношение к смещению foo2.f
? foo1
и foo2
— разные типы.
Это разные типы, но компилятор мог бы обращаться с ними одинаково в отношении заполнения, если бы не указанное вами правило.
На вопрос: «Разве он не будет иметь такое же выравнивание, как FAM, и, следовательно, такое же смещение, как FAM?»: Это не требование стандарта C.