С точки зрения языкового юриста, это обычно рассматривается как недопустимый код, потому что массивы целых чисел имеют только размер 10, а код имеет доступ к сверх объявленного размера массива . Но раньше это была обычная идиома, и я не знаю компилятора, который бы ее не принял. Тем не менее, со всеми компиляторами реального мира, которые я знаю, результирующая программа будет иметь ожидаемое поведение.
После второго (на самом деле гораздо большего) чтения проекта стандарта C11 (n1570) цель стандарта все еще не Чисто. 6.2.5 Типы В § 20 говорится:
Тип массива описывает непрерывно выделенный непустой набор объектов с определенным типом объекта-члена, называемым типом элемента.
Показывает, что массив содержит непрерывно размещенные объекты. Но ИМХО неясно, является ли непрерывно выделенный набор объектов массивом.
Если вы отвечаете «нет», то показанный код вызывает UB, обращаясь к массиву за последним элементом
Но если вы отвечаете «да», тогда набор из 10 смежных наборов из 10 смежных целых чисел дает 100 смежных целых чисел и может рассматриваться как массив из 100 целых чисел. Тогда показанный код будет законным.
Это последнее допущение кажется обычным в реальном слове, потому что оно согласуется с динамическим c распределением массива: вы выделяете достаточно памяти для ряда объектов, и вы можете доступ к нему, как если бы он был объявлен как массив - и функция распределения гарантирует отсутствие проблем с выравниванием.
Мой вывод на данный момент:
- это красивый и чистый код: конечно нет, и я бы избегал его в производственном коде
- вызывает ли он UB: я действительно не знаю и мое личное мнение, вероятно, нет
Давайте посмотрим на код, добавленный при редактировании:
array[0][11] = 42;
Компилятор знает, что массив объявлен как int[10][10]
. Таким образом, он знает, что оба индекса должны быть меньше 10, и может вызвать предупреждение.
int* first_element = array[0];
first_element[11] = 42;
first_element
объявлен как простой указатель. Статически компилятор должен предположить, что он может указывать внутри массива неизвестного размера, поэтому вне определенного контекста c гораздо сложнее подать предупреждение. Конечно, для человека-программиста очевидно, что оба пути должны рассматриваться одинаково, но, поскольку компилятор не обязан выдавать какие-либо диагнозы c для массива вне границ, усилия по их обнаружению сведены к минимуму и только тривиальны. случаев обнаружены.
Вдобавок, когда компилятор внутренне кодирует арифметику указателей на обычных платформах, он просто вычисляет адрес памяти, который является исходным адресом и байтовым смещением . Таким образом, он может выдавать тот же код, что и:
char *addr = (char *) first_element; // (1)
addr += 11 * sizeof(int); // (2)
*((int *) addr) = 42; // (3)
(1) является допустимым, потому что указатель на любой объект (здесь int) может быть преобразован в указатель на char, который требуется для указания на первый байт представления объекта
(2) трюк здесь в том, что (char *) first_element
совпадает с (char *) array
, потому что первый байт массива 10 * 10 равен первый байт первого int первой строки, и у одного байта может быть только один единственный адрес. Поскольку размер array
равен 10 * 10 * sizeof(int)
, 11 * sizeof(int)
является допустимым смещением в нем.
(3) по той же причине, (char *) &array[1][1]
равно addr, потому что элементы в массиве являются смежными, поэтому их байтовые представления также являются смежными. А поскольку прямое и обратное преобразование между двумя типами является допустимым и требуется для возврата исходного указателя, (int *) addr
равно (int*) ((char*) &array[1][1])
. Это означает, что разыменование (int *) addr
допустимо и будет иметь тот же эффект, что и array[1][1] = 42
.
Это не означает, что first_element[11]
не включает UB. array[0]
имеет заявленный размер, равный 10. Это просто объясняет, почему все известные компиляторы принимают его (в дополнение к нежеланию нарушать унаследованный код).