Пересекающийся конец массива с указателем на массив
Правильно ли этот код?
int arr[2];
int (*ptr)[2] = (int (*)[2]) &arr[1];
ptr[0][0] = 0;
Очевидно, что ptr[0][1]
будет недопустимым, обратившись за пределы arr
.
Примечание. Нет сомнений в том, что ptr[0][0]
обозначает ту же ячейку памяти, что и arr[1]
; возникает вопрос, разрешено ли нам получать доступ к этому местоположению памяти через ptr
. Здесь - еще несколько примеров того, когда выражение обозначает одно и то же место в памяти, но ему не разрешается обращаться к этому месту памяти.
Примечание 2: Также рассмотрите **ptr = 0;
. Как отметил Марк Ван Леувен, ptr[0]
эквивалентно *(ptr + 0)
, однако ptr + 0
, похоже, выпадает из-за арифметического раздела указателя. Но используя *ptr
вместо этого, этого можно избежать.
Ответы
Ответ 1
Не ответ, а комментарий, который я не могу сказать хорошо, не будучи стеной текста:
Приведенные массивы гарантированно сохраняют их содержимое смежно, чтобы их можно было "повторить" с помощью указателя. Если я могу взять указатель на начало массива и последовательно увеличивать этот указатель до тех пор, пока я не получу доступ к каждому элементу массива, то, несомненно, это сделает утверждение, что к массиву можно получить серию любого типа, из которого он состоит.
Конечно, комбинация:
1) Array [x] сохраняет свой первый элемент в массиве адресов 'array'
2) Последовательные приращения указателя к нему достаточны для доступа к следующему элементу
3) Массив [x-1] подчиняется тем же правилам
Тогда должно быть законно, по крайней мере, взглянуть на адрес "массив", как если бы это был массив типов [x-1] вместо массива типов [x].
Кроме того, учитывая, что точки должны быть смежными и как должны вести себя указатели на элементы в массиве, конечно, должно быть законным, чтобы затем группировать любое непрерывное подмножество массива [x] в виде массива [y], где y < x и верхняя граница не превышает размер массива [x].
Не будучи юристом по языку, я просто издеваюсь над мусором. Меня очень интересует итог этой дискуссии.
EDIT:
При дальнейшем рассмотрении исходного кода мне кажется, что массивы сами по себе во многом являются особым случаем. Они распадаются на указатель, и я считаю, что это может быть псевдонимом в соответствии с тем, что я только что сказал ранее в этом сообщении.
Таким образом, без каких-либо стандартов, чтобы поддержать мое скромное мнение, массив не может быть действительно недействительным или "undefined" в целом, если он действительно не воспринимается как единое целое.
То, что обрабатывается равномерно, - это отдельные элементы. Поэтому я думаю, что имеет смысл говорить только о том, является ли доступ к определенному элементу действительным или определенным.
Ответ 2
Для С++ (я использую проект N4296) [dcl.array]/7
говорит, в частности, что если результатом подписи является массив, он сразу преобразуется в указатель. То есть, в ptr[0][0]
ptr[0]
сначала преобразуется в int*
, и к нему применяется только второй [0]
. Так что это совершенно правильный код.
Для C (проект C15 проекта N1570) 6.5.2.1/3
указывает то же самое.
Ответ 3
Да, это правильный код. Цитирование N4140 для С++ 14:
[expr.sub]/1... Выражение E1[E2]
идентично (по определению) до *((E1)+(E2))
[expr.add]/5... Если оба операнда указателя и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, оценка не должна приводить к переполнению; в противном случае поведение undefined.
Здесь нет переполнения. &*(*(ptr)) == &ptr[0][0] == &arr[1]
.
Для C11 (N1570) правила одинаковы. §6.5.2.1 и §6.5.6
Ответ 4
Позвольте мне дать особое мнение: это (по крайней мере, в С++) поведение undefined по той же причине, что и в другом вопросе, связанным с этим вопросом.
Сначала позвольте мне пояснить пример с некоторыми typedefs, которые упростят обсуждение.
typedef int two_ints[2];
typedef int* int_ptr;
typedef two_ints* two_ints_ptr;
two_ints arr;
two_ints_ptr ptr = (two_ints_ptr) &arr[1];
int_ptr temp = ptr[0]; // the two_ints value ptr[0] gets converted to int_ptr
temp[0] = 0;
Итак, вопрос заключается в том, есть ли объект типа two_ints
, адрес которого совпадает с адресом arr[1]
(в том же смысле, что адрес arr
совпадает с адресом arr[0]
) и поэтому нет объекта, к которому возможно указать ptr[0]
, тем не менее можно преобразовать значение этого выражения в один из типов int_ptr
(здесь с именем temp
), который указывает на объект (а именно, целочисленный объект также называется arr[1]
).
Точка, в которой я думаю, что поведение undefined находится в оценке ptr[0]
, что эквивалентно (в 5.2.1 [expr.sub]) до *(ptr+0)
; более точно оценка ptr+0
имеет поведение undefined.
Я приведу свою копию С++, которая не является официальной [N3337], но, вероятно, язык не изменился; меня немного беспокоит, что номер раздела совсем не совпадает с номером, указанным в принятом ответе связанного вопроса. Во всяком случае, для меня это §5.7 [expr.add]
Если оба операнда указателя и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, оценка не должна приводить к переполнению; в противном случае поведение undefined.
Так как операнд указателя ptr
имеет указатель на two_ints
, "объект массива", упомянутый в цитированном тексте, должен быть массивом объектов two_ints
. Однако здесь существует только один такой объект: фиктивный массив, чей уникальный элемент arr
, который мы должны вызвать в таких ситуациях (как: "указатель на объект nonarray ведет себя так же, как указатель на первый элемент массив длины один..." ), но ясно, что ptr
не указывает на его уникальный элемент arr
. Таким образом, хотя ptr
и ptr+0
, без сомнения, являются равными значениями, ни один из них вообще не указывает на элементы любого объекта массива (даже не фиктивный), ни один конец конца такого объекта массива, а условие указанной фразы не выполняется. Следствием является (не то, что происходит переполнение, но), что поведение undefined.
Итак, поведение уже undefined до применения оператора косвенности *
. Я бы не стал спорить о поведении undefined из последней оценки, хотя фраза "результат - это lvalue, относящаяся к объекту или функции, к которой относится точка выражения", трудно интерпретировать для выражений, которые не относятся к какому-либо объекту в все. Но я был бы мягким в интерпретации этого, так как я считаю, что разыменование указателя мимо массива не должно быть undefined поведение (например, если оно используется для инициализации ссылки).
Это предполагает, что если вместо ptr[0][0]
писать (*ptr)[0]
или **ptr
, то поведение не будет undefined. Это любопытно, но это не первый случай, когда меня удивляет стандарт С++.
Ответ 5
Это зависит от того, что вы подразумеваете под "правильным". Вы делаете бросок на ptr до arr[1]
. В С++ это, вероятно, будет reinterpret_cast
. C и С++ - это языки, которые (в большинстве случаев) предполагают, что программист знает, что он делает. Что этот код багги не имеет ничего общего с тем фактом, что он является допустимым кодом C/С++.
Вы не нарушаете никаких правил в стандартах (насколько я вижу).
Ответ 6
Попытка ответить здесь, почему код работает на часто используемых компиляторах:
int arr[2];
int (*ptr)[2] = (int (*)[2]) &arr[1];
printf("%p\n", (void*)ptr);
printf("%p\n", (void*)*ptr);
printf("%p\n", (void*)ptr[0]);
Все строки печатают один и тот же адрес на часто используемых компиляторах. Таким образом, ptr
- это объект, для которого *ptr
представляет собой ту же ячейку памяти, что и ptr
для обычно используемых компиляторов, и поэтому ptr[0]
действительно является указателем на arr[1]
, и поэтому arr[0][0]
есть arr[1]
. Таким образом, код присваивает значение arr[1]
.
Теперь предположим, что существует порочная реализация, где указатель на массив (ПРИМЕЧАНИЕ: я говорю указатель на массив, то есть &arr
, который имеет тип int(*)[]
, а не arr
, что означает то же, что &arr[0]
и имеет тип int*
) - это указатель на второй байт внутри массива. Тогда разыменование ptr
совпадает с вычитанием 1 из ptr
с использованием арифметики char*
. Для структур и объединений гарантируется, что указатель на такие типы будет таким же, как указатель на первый элемент таких типов, но в указатель на указатель на массив в указатель нет была найдена гарантия для массивов (т.е. указатель на массив будет таким же, как указатель на первый элемент массива), и на самом деле @FUZxxl планировал записать отчет о дефекте по стандарту. Для такой порочной реализации *ptr
i.e. ptr[0]
не будет таким же, как &arr[1]
. На RISC-процессорах это на самом деле вызовет проблемы из-за выравнивания данных.
Дополнительная забава:
int arr[2] = {0, 0};
int *ptr = (int*)&arr;
ptr[0] = 5;
printf("%d\n", arr[0]);
Должен ли этот код работать? Он печатает 5.
Еще веселее:
int arr[2] = {0, 0};
int (*ptr)[3] = (int(*)[3])&arr;
ptr[0][0] = 6;
printf("%d\n", arr[0]);
Должны ли это работать? Он печатает 6.
Это должно работать:
int arr[2] = {0, 0};
int (*ptr)[2] = &arr;
ptr[0][0] = 7;
printf("%d\n", arr[0]);