Пересекающийся конец массива с указателем на массив - программирование
Подтвердить что ты не робот

Пересекающийся конец массива с указателем на массив

Правильно ли этот код?

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 вместо этого, этого можно избежать.

4b9b3361

Ответ 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]);