C99 гарантирует, что массивы непрерывны?

После горячего потока комментария в другом вопросе я пришел к дебатам того, что и что не определяется в стандарте C99 о массивах C.

В основном, когда я определяю 2D массив как int a[5][5], делает стандартную гарантию C99 или не, что это будет непрерывный блок ints, могу я бросать его к (int *)a и убедитесь, что у меня будет допустимое 1D массив 25 ints.

Поскольку я понимаю стандарт, вышеупомянутое свойство неявно в sizeof определении и в адресной арифметике с указателями, но другие, кажется, не соглашаются и говорят, что кастинг к (интервал*) вышеупомянутая структура дает неопределенное поведение (даже если они соглашаются, что все существующие реализации на самом деле выделяют непрерывные значения).

Строго говоря, если бы мы думаем реализация, которая оснастила бы массивы, чтобы проверить границы массива на все размеры и возвратить некоторую ошибку при доступе 1D массив, или не предоставляет корректный доступ к элементам выше 1-й строки. Такая реализация могла быть стандартная совместимый? И в этом случае какие части стандарта C99 релевантны.

17
задан kriss 14 May 2010 в 09:13
поделиться

3 ответа

Мы должны начать с изучения того, что на самом деле представляет собой int a [5] [5]. Используемые типы:

  • int
  • массив [5] целых чисел
  • массив [5] массивов

Нет задействованного массива [25] целых чисел.

Верно, что семантика sizeof подразумевает, что массив в целом является непрерывным. Массив [5] целых чисел должен иметь 5 * sizeof (int), и при рекурсивном применении [5] [5] должен иметь 5 * 5 * sizeof (int). Нет места для дополнительной набивки.

Кроме того, массив в целом должен работать при передаче в memset, memmove или memcpy с размером sizeof. Также должна быть возможность перебирать весь массив с помощью (char *). Итак, допустимая итерация:

int  a[5][5], i, *pi;
char *pc;

pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
    pi = (int *)pc;
    DoSomething(pi);
    pc += sizeof(int);
}

То же самое с (int *) будет неопределенным поведением, потому что, как сказано, здесь не задействован массив [25] из int. Использование союза, как в ответе Кристофа, тоже должно быть допустимым. Но есть еще один момент, еще больше усложняющий это, - оператор равенства:

6.5.9.6 Два указателя сравниваются равными тогда и только тогда, когда оба являются нулевыми указателями, оба являются указателями на один и тот же объект (включая указатель на объект и подобъект в его начале) или функцию, оба являются указателями на один за последним элементом того же массива объект, или один - указатель на один за концом одного объекта массива, а другой - указатель на начало другого объекта массива, который сразу же следует за первым объектом массива в адресном пространстве. 91)

91) Два объекта могут быть смежными в памяти, потому что они являются смежными элементами большего массива или соседними элементами структуры без заполнения между ними, или потому что реализация решила разместить их таким образом, даже если они не связаны. Если предыдущие операции с недопустимыми указателями (например, доступ за пределы массива) приводили к неопределенному поведению, последующие сравнения также производили неопределенное поведение.

Это означает:

int a[5][5], *i1, *i2;

i1 = &a[0][0] + 5;
i2 = &a[1][0];

i1 сравнивается как i2. Но при итерации по массиву с (int *) поведение по-прежнему не определено, поскольку оно изначально получено из первого подмассива. Он не преобразуется волшебным образом в указатель на второй подмассив.

Даже при этом

char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int  *i3 = (int *)c;

не поможет. Он сравнивается с i1 и i2, но не получен ни из одного подмассива; это указатель на один int или в лучшем случае массив [1] из int.

Я не считаю это ошибкой в ​​стандарте. Это наоборот: если это разрешить, возникнет особый случай, который нарушит либо систему типов для массивов, либо правила арифметики указателей, либо и то, и другое. Это можно считать отсутствующим определением, но не ошибкой.

Таким образом, даже если структура памяти для [5] [5] идентична структуре [25], и тот же самый цикл с использованием (char *) может использоваться для итерации по обоим, реализация будет позволено взорваться, если один используется в качестве другого. Я не знаю, почему он должен или знаю какую-либо реализацию, которая могла бы, и, возможно, в Стандарте есть один факт, не упомянутый до сих пор, который делает его четко определенным поведением.А пока я буду считать его неопределенным и буду в безопасности.

18
ответ дан 30 November 2019 в 12:26
поделиться

Я добавил еще несколько комментариев к нашему исходному обсуждению . Семантика

sizeof подразумевает, что int a [5] [5] является смежным, но обращение ко всем 25 целым числам посредством увеличения указателя типа int * p = * a является неопределенное поведение: арифметика указателей определяется только до тех пор, пока все задействованные указатели лежат внутри (или на один элемент после последнего элемента) одного и того же массива, например, & a [2] [1] и & a [3] [1] нет (см. Раздел 6.5.6 C99).

В принципе, это можно обойти, приведя & a с типом int (*) [5] [5] к int (*) [ 25] . Это законно согласно 6.3.2.3 §7, так как не нарушает никаких требований к выравниванию. Проблема в том, что доступ к целым числам через этот новый указатель является недопустимым, поскольку он нарушает правила псевдонима в 6.5 §7. Вы можете обойти это, используя объединение union для набора текста (см. Сноску 82 в TC3):

int *p = ((union { int multi[5][5]; int flat[25]; } *)&a)->flat;

Насколько я могу судить, это C99, соответствующий стандартам.

11
ответ дан 30 November 2019 в 12:26
поделиться

Если массив статический, например массив int a [5] [5] , он гарантированно будет непрерывным.

2
ответ дан 30 November 2019 в 12:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: