Гарантируется ли сохранение элементов массива в C последовательно, без заполнения?

Другими словами: гарантировано ли, что если у меня есть массив, выделенный таким образом:

void *arr = calloc(nmemb, sizeof(some_type)) 

Тогда eltb , eltb , eltc будут указывать на одно и то же место в памяти, которое будет вторым элементом типа some_type этого массива?

 some_type *elta = &((some_type*)arr)[1]; some_type *eltb = ((some_type*)arr)+1; some_type *eltc = (char*)arr+sizeof(some_type); 

Причина, по которой я спрашиваю об этом, заключается в том, что я пытаюсь сделать «контейнер» на C, и если это не выполняется, я не понимаю, как вернуть указатель на любой другой элемент, чем первый.

Да, это гарантировано. Если добавляются байты заполнения, они добавляются внутри struct some_type , но не между двумя элементами массива.

E. g .:

 struct S { int n; short s; // this is just for illustration WHERE byte padding (typically) would occur!!! #if BYTE_ALIGNMENT >= 4 unsigned char : 0; unsigned char : 0; #endif }; struct S s[2]; size_t d = (char*)(s + 1) - (char*)s; 

При выравнивании по байтам, скорректированном до 4 или 8 (или даже больших степеней 2), эта структура будет иметь размер 8, а d будет равно 8, с установкой байт в 1 или 2, структура будет иметь размер 6 так же, как и быть …

Примечание. Это не единственное место, где могут возникать байты заполнения. Если вы переключили элементы n и s , для заполнения n нужно было бы отбирать байты между s и n чтобы правильно выровнять n . С другой стороны, никакие байты заполнения не понадобятся после того, как n больше, так как размер структуры обеспечит правильное выравнивание.

Ссылаясь на стандарт: C11, 6.2.5.20:

Тип массива описывает смежно выделенный непустой набор объектов с конкретным типом объекта-члена, называемый типом элемента. 36) Типы массивов характеризуются их типом элемента и количеством элементов в массиве. […]

(Подчеркиваю!).

Выравнивание данных является сложным делом, как показано на следующем примере (который вы можете даже сделать, чтобы сделать свои собственные эксперименты):

 #include  struct A { /* no padding */ char a[3]; }; struct B { int a; char b[3]; /* one byte of padding (in 32bit arch) added here */ }; struct C { /* no padding again */ char a[4]; char b[3]; }; struct D { char a[3]; /* one byte of padding to ensure alignment of next field */ int b; char c[3]; /* one byte of padding to ensure alignment of whole struct in arrays */ } #define P(X) printf("sizeof struct %s == %ld\n", #X, sizeof (struct X)) int main() { P(A); P(B); P(C); P(D); } /* main */ 

Как вы видите, требуемое выравнивание (1 байт) типа struct A позволяет разместить его в любом месте в памяти и определяет, что для определения значения sizeof не требуются байты заполнения.

Во втором примере мы ввели значение int , которое заставляет всю struct быть выровненной по слову (таким образом, int доступен по правильно выровненному адресу памяти), так что на этот раз компилятор заполнил (в конце структуры) для дополнительные байты, поэтому любой тип массива указанного типа будет правильно выровнен, а его поле int будет доступно по допустимому адресу.

В третьем примере я иллюстрирую, что эффект отступов во втором примере связан с полем int присутствующим в структуре, поскольку третий пример имеет одно и то же поле размера, но на этот раз не требует поля выравнивания, поэтому никакого дополнения был вставлен, так как все отдельные типы данных внутри нуждаются в выравнивании 1.

Выход (в моей системе, система MAC OSX):

 sizeof struct A == 3 sizeof struct B == 8 sizeof struct C == 7 sizeof struct D == 12