Подтвердить что ты не робот

Является ли `* ((* (& array + 1)) - 1)` безопасным для использования, чтобы получить последний элемент автоматического массива?

Предположим, я хочу получить последний элемент автоматического массива, размер которого неизвестен. Я знаю, что я могу использовать оператор sizeof для получения размера массива и получения последнего элемента соответственно.

Использует *((*(&array + 1)) - 1) безопасно?

Как

char array[SOME_SIZE] = { ... };
printf("Last element = %c", *((*(&array + 1)) - 1));
int array[SOME_SIZE] = { ... };
printf("Last element = %d", *((*(&array + 1)) - 1));

и т.д.

4b9b3361

Ответ 1

Я считаю, что поведение undefined по причинам, о которых Питер упоминает в своем ответе.

Прошло около десяти дней. С одной стороны, разыменование &array + 1 представляется законным, поскольку оно только меняет тип с T (*)[] на T [], но, с другой стороны, оно все еще является указателем на неинициализированную, неиспользуемую и нераспределенную память.

Мой ответ основывается на следующем:

C99 6.5.6.7 (Семантика аддитивных операторов)

Для целей этих операторов указатель на объект, который является не элемент массива ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта как его тип элемента.

Так как &array не является указателем на объект, который является элементом массива, то в соответствии с этим это означает, что код эквивалентен:

char array_equiv[1][SOME_SIZE] = { ... };
/* ... */
printf("Last element = %c", *((*(&array_equiv[0] + 1)) - 1));

То есть &array является указателем на массив из 10 символов, поэтому он ведет себя так же, как указатель на первый элемент массива длины 1, где каждый элемент представляет собой массив из 10 символов.

Теперь, что вместе с следующим пунктом (уже упомянутым в других ответах, этот точный отрывок явно украден из ответа ameyCU):

C99 Раздел 6.5.6.8 -

[...]
если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает [...]
Если результат указывает один за последний элемент объекта массива, он не должен использоваться как операнд унарного * оператора, который оценивается.

Делает это довольно ясно, что это UB: это эквивалентно разыменованию указателя, который указывает один за последним элементом array_equiv.

Да, в реальном мире это, вероятно, работает, так как на самом деле исходный код на самом деле не разыскивает местоположение памяти, это в основном преобразование типа от T (*)[] до T [], но я уверен, что из строгое соответствие стандарту, это поведение undefined.

Ответ 2

Нет, это не так.

&array имеет указатель типа на char[SOME_SIZE] (в приведенном первом примере). Это означает, что &array + 1 указывает на память сразу после конца array. Разыменование того, что (как в (*(&array+1)) дает поведение undefined.

Нет необходимости анализировать дальше. Когда есть какая-либо часть выражения, которое дает поведение undefined, все выражение делает.

Ответ 3

Я не думаю, что это безопасно.

Из стандартного @dasblinkenlight указанного в его ответе (теперь удалено), есть также что-то, что я хотел бы добавить:

C99 Раздел 6.5.6.8 -

[...]
если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает [...]
Если результат указывает один за последний элемент объекта массива, он не должен использоваться как операнд унарного * оператора, который оценивается.

Итак, как говорится, мы не должны делать это *(&array + 1), поскольку он будет проходить мимо последнего элемента массива и поэтому * не должен использоваться.

Как также хорошо известно, что указатели разыменования, указывающие на неавторизованное расположение памяти, приводят к поведению undefined.

Ответ 4

Возможно, это безопасно, но есть некоторые оговорки.

Предположим, что

T array[LEN];

Тогда &array имеет тип T(*)[LEN].

Далее, &array + 1 снова имеет тип T(*)[LEN], указывая только на конец исходного массива.

Далее, *(&array + 1) имеет тип T[LEN], который может быть неявно преобразован в T*, все еще указывая только на конец исходного массива. (Таким образом, мы НЕ разыменовали недопустимую ячейку памяти: оператор * не оценивается).

Далее, *(&array + 1) - 1 имеет тип T*, указывая на последнее расположение массива.

Наконец, мы разыскиваем это (что является законным, если длина массива не равна нулю): *(*(&array + 1) - 1) дает последний элемент массива, значение типа T.

Обратите внимание, что единственный раз, когда мы на самом деле разыскиваем указатель, находится на этом последнем этапе.

Теперь потенциальные оговорки.

Во-первых, *(&array + 1) формально появляется как попытка разыменовать указатель, указывающий на недопустимое расположение памяти. Но это действительно так. Что характер указателей массива: это формальное разыменование только изменяет тип указателя, на самом деле не приводит к попытке получить значение из ссылочного местоположения. То есть array имеет тип T[LEN], но он может быть неявно преобразован в тип &T, указывая на первый элемент массива; &array - это указатель на тип T[LEN], указывающий на начало массива; *(&array+1) снова имеет тип T[LEN], который может быть неявно преобразован в тип &T. Ни в коем случае не указатель на самом деле разыменован.

Во-вторых, &array + 1 может на самом деле быть недопустимым адресом, но это действительно не так: справочное руководство My С++ 11 прямо говорит мне, что "получение указателя на элемент один за концом массива гарантировано работать", и аналогичное утверждение также сделано в K & R, поэтому я считаю, что это всегда было стандартным поведением.

Наконец, в случае массива нулевой длины выражение разыскивает расположение памяти непосредственно перед массивом, которое может быть нераспределенным/недействительным. Но эта проблема также возникла бы, если бы вы использовали более традиционный подход, используя sizeof() без предварительного тестирования для ненулевой длины.

Короче говоря, я не верю, что есть что-либо undefined или зависящее от реализации поведение этого выражения.

Ответ 5

Imho, который может работать, но, вероятно, неразумный. Вы должны тщательно просмотреть свой дизайн sw и спросить себя, почему вы хотите получить последнюю запись массива. Является ли содержимое массива полностью неизвестным вам или можно определить структуру в терминах c structs и union. Если это так, избегайте сложных операций указателя в массиве char, например, и правильно определяйте данные в коде c, в структурах и объединениях, где это возможно.

Итак, вместо:

 printf("Last element = %c", *((*(&array + 1)) - 1));

Это может быть:

 printf("Checksum = %c", myStruct.MyUnion.Checksum);

Это уточняет ваш код. Последнее письмо в вашем массиве ничего не значит для человека, не знакомого с тем, что в этом массиве. myStruct.myUnion.Checksum имеет смысл для всех. Изучение структуры myStruct может объяснить всю структуру данных кому угодно. Пожалуйста, используйте что-то подобное, если оно может быть объявлено таким образом. Если вы находитесь в редкой ситуации, вы не можете, изучите выше ответы, они имеют смысл, я думаю,

Ответ 6

  • array - это массив типа int[SOME_SIZE]
  • &array является указателем типа int(*)[SOME_SIZE]
  • &array + 1 - это простейший указатель типа int(*)[SOME_SIZE]
  • *(&array + 1) - это значение типа int[SOME_SIZE]

Другие ответы уже процитировали соответствующие части стандарта, но я думаю, что последняя маркерная точка может помочь устранить путаницу.

Смятение, похоже, связано с идеей о том, что разыменование &array + 1 приводит к прошлому концу int*, который звучит так, как будто он должен быть разумным, даже если стандарт технически запрещает его.

Но это не то, что происходит: разыменование происходит, пытаясь произвести lvalue (по существу, ссылку) на несуществующий объект типа int[SOME_SIZE], что действительно не должно звучать разумно.


Даже если это было определено поведение, а не использовать тайный трюк, гораздо лучше сделать что-то разборчивое,

template< typename T, size_t N >
T& last(T (&array)[N])
{
    return array[N-1];
}

// ...

int array[SOME_SIZE] = { ... };
printf("Last element = %d", last(array));

Ответ 7

а)

Если оба операнда указателя и результат [P + N] указывают на элементы одного и того же объекта массива или один за последним элементом объект массива, оценка не должна приводить к переполнению;
[...]
если выражение P указывает либо на элемент массива объект или один за последним элементом объекта массива, а выражение Q указывает на последний элемент одного и того же объекта массива, выражение ((Q) +1) - (P) имеет то же значение, что и ((Q) - (P)) + 1 и как - ((P) - ((Q) +1)) и имеет значение 0, если выражение P указывает один мимо последнего элемента объекта массива, хотя выражение (Q) +1 не указывает на элемент объекта массива.

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

Тогда нам нужно позаботиться об этой части:

Если результат указывает один за последний элемент объекта массива, он не будет использоваться как операнд унарного * оператора, который оценены.

Есть одна важная часть, которую остальные ответы опущены, а именно:

Если операнд указателя указывает на элемент объекта массива

Это не факт. Указатель операнда мы разыскиваем не указатель на элемент объекта массива, он является указателем на указатель. Итак, вся эта статья совершенно не имеет значения. Но также говорится:

Для целей этих [аддитивных] операторов указатель на объект, который является не элемент массива ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта как его тип элемента.

Что это значит?

Это означает, что наш указатель на указатель на самом деле снова является указателем на массив - длины [1]. И теперь мы можем закрыть цикл, потому что, как сказано в первом абзаце, нам разрешено делать вычисления с одним из массива, поэтому нам разрешено делать вычисления с массивом, как если бы это был массив длины [2]!

Более графически:

ptr -> (ptr to int[10])[0] -> int[10]
    -> (ptr to int[10])[1]

Итак, нам разрешено делать вычисления с (ptr до int [10]) [1], хотя это технически вне массива длины [1].

б)

Выполняются следующие шаги:

array ptr типа int [SOME_SIZE] в первый массив элементов

&array ptr для ptr типа int [SOME_SIZE] для первого элемента массива

+ 1 ptr, более чем ptr типа int [SOME_SIZE]) в первый массив элементов, в ptr типа int

Это NOT, но указатель на int [SOME_SIZE + 1], согласно разделу C99 6.5.6.8. Это НЕ еще ptr + SOME_SIZE + 1

* Мы разыскиваем указатель на указатель. СЕЙЧАС, после разыменования мы имеем указатель в соответствии с разделом 6.5.6.8 C99, который проходит мимо элемента массива и который не может быть разыменован. Этот указатель допускается к существованию, и нам разрешено использовать на нем операторы, кроме унарного * оператора. Но мы еще не используем этот указатель на этом указателе.

-1 Теперь мы вычитаем один из ptr типа int в один после последнего элемента массива, позволяя ptr указывать на последний элемент массива.

* разыменование ptr до int последнему элементу массива, что является законным.

с)

И последнее, но не менее важное:

Если это было бы незаконным, макрос offset тоже был бы незаконным, который определяется как:
((size_t)(&((st *)0)->m))