Является беззнаковым символом [4] [5]; а [1] [7]; неопределенное поведение? - PullRequest
9 голосов
/ 22 сентября 2010

Один из примеров неопределенного поведения из стандарта C гласит (J.2):

- индекс массива выходит за пределы диапазона, даже если объект явно доступен с данным индексом(как в выражении lvalue a [1] [7] с учетом объявления int a [4] [5]) (6.5.6)

Если объявление изменяется с int a[4][5] на unsigned char a[4][5], доступ к a[1][7] все еще приводит к неопределенному поведению?Мое мнение таково, что это не так, но я слышал от других, кто не согласен с этим, и я хотел бы узнать, что думают некоторые другие потенциальные эксперты по SO.

В соответствии с обычной интерпретацией пункта 6.2.6.1 и пункта 7 6.5, представление объекта a имеет размер sizeof (unsigned char [4][5])*CHAR_BIT битов и может быть доступно как массив типа unsigned char [20], перекрываемый сobject.

a[1] имеет тип unsigned char [5] как lvalue, но используется в выражении (как операнд для оператора [] или эквивалентно как операнд для *Оператор 1026 * в *(a[1]+7)), он распадается на указатель типа unsigned char *.

Значение a[1] также является указателем на байт "представления"a в форме unsigned char [20].Таким образом, добавление 7 к a[1] действительно.

Ответы [ 5 ]

4 голосов
/ 22 сентября 2010

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

Цитировать мой комментарий из нашего последнего обсуждения ( Гарантирует ли C99, что массивы смежны? )

"Ваш первоначальный вопрос был для a[0][6], с объявлением char a[5][5]. Это UB, несмотря ни на что. Допустимо использовать char *p = &a[3][4]; и доступ p[0] к p[5]. Получение адреса &p[6] все еще действителен, но доступ к p[6] находится вне объекта, то есть UB. Доступ к a[0][6] находится вне объекта a[0], который имеет массив типов [5] из символов. Тип результата не имеет значения , важно, как вы его достигнете. "

EDIT:

Существует достаточно случаев неопределенного поведения, когда вам приходится просматривать весь Стандарт, собирать факты и объединять их, чтобы, наконец, прийти к выводу о неопределенном поведении. Это явное , и вы даже цитируете предложение Стандарта в своем вопросе. Он явный и не оставляет места для обходных путей.

Мне просто интересно, сколько еще ясности в рассуждениях вы ожидаете от нас, чтобы убедиться, что это действительно UB?

РЕДАКТИРОВАТЬ 2:

После ознакомления со Стандартом и сбора информации, есть еще одна релевантная цитата:

6.3.2.1 - 3: За исключением случаев, когда это операнд оператора sizeof или унарный оператор &, или строка литерал, используемый для инициализации массива, выражение, которое имеет тип ‘‘ массив тип ’’ преобразуется в выражение с типом «указатель на тип», который указывает на начальный элемент объект массива и не является значением l . Если объект массива имеет хранилище регистров класс, поведение не определено.

Так что я думаю, что это действительно:

unsigned char *p = a[1]; 
unsigned char c = p[7]; // Strict aliasing not applied for char types

Это UB:

unsigned char c = a[1][7];

Потому что a[1] не является lvalue в этой точке, но оценивается далее, нарушая J.2 с индексом массива вне диапазона. Что в действительности происходит, должно зависеть от того, как компилятор на самом деле реализует индексирование массива в многомерных массивах. Таким образом, вы можете быть правы, что это не имеет никакого значения для каждой известной реализации. Но это также допустимое неопределенное поведение. ;)

4 голосов
/ 22 сентября 2010

Я бы прочитал этот «информативный пример» в J2 как намек на то, что хотел стандартный орган: не полагайтесь на тот факт, что случайное вычисление индекса массива дает что-то внутри границ «массива представления». Цель состоит в том, чтобы гарантировать, что все отдельные границы массива всегда должны находиться в определенных диапазонах.

В частности, это позволяет реализации выполнять агрессивную проверку границ и лаять на вас во время компиляции или во время выполнения, если вы используете a[1][7].

Это рассуждение не имеет ничего общего с базовым типом.

1 голос
/ 22 сентября 2010

от 6,5,6 / 8

Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива , при оценке не должно быть переполнения ; в противном случае поведение не определено.

В вашем примере a [1] [7] указывает ни на тот же объект массива a [1], ни на один после последнего элемента из [1], так что это неопределенное поведение.

0 голосов
/ 22 сентября 2010

Под капотом, на фактическом машинном языке, нет никакой разницы между a[1][7] и a[2][2] для определения int a[4][5].Как сказал R .., это связано с тем, что доступ к массиву транслируется в 1 * sizeof(a[0]) + 7 = 12 и 2 * sizeof(a[0]) + 2 = 12 (конечно, * sizeof(int)).Машинный язык ничего не знает о массивах, матрицах или индексах.Все это знает об адресах.Приведенный выше C-компилятор может делать все, что пожелает, включая проверку наивных границ на индексаторе - тогда a[1][7] будет выходить за границы, поскольку массив a[1] не имеет 8 ячеек.В этом отношении нет разницы между int и char или unsigned char.

Я предполагаю, что разница заключается в строгих правилах алиасинга между int и char - даже если программист на самом деле не делает ничего плохого, компилятор вынужден делать "логический" типприведение к массиву, который он не должен делать.Как сказал Дженс Гастт, это больше похоже на способ включения строгих проверок границ, а не на реальные проблемы с int или char.

Я немного поиграл с компилятором VC ++, и кажетсявести себя так, как вы ожидаете.Кто-нибудь может проверить это с gcc?По моему опыту gcc гораздо строже в таких вещах.

0 голосов
/ 22 сентября 2010

Я полагаю, что причина того, что цитируемый образец (J.2) является неопределенным поведением, заключается в том, что компоновщик не обязан помещать подмассивы a [1], a [2] и т.д. рядом друг с другом в памяти,Они могут быть разбросаны по памяти или могут быть смежными, но не в ожидаемом порядке.Переключение базового типа с int на unsigned char не меняет ничего из этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...