Получение указателя «конец в конец» с использованием адреса массива

В C и C ++ часто полезно использовать указатель конца конца для написания функций, которые могут работать с произвольно большими массивами. C ++ дает std::end перегрузка, чтобы сделать это проще. В C, с другой стороны, я обнаружил, что довольно часто можно увидеть макрос, определенный и используемый следующим образом:

#define ARRAYLEN(array) (sizeof(array)/sizeof(array[0]))

// ...

int a [42];
do_something (a, a + ARRAYLEN (a));

Я также видел арифметический трюк с указателем, который позволял таким функциям работать с отдельными объектами:

int b;
do_something (&b, &b + 1);

Мне пришло в голову, что нечто подобное можно сделать с массивами, так как они рассматриваются C (и, я полагаю, C ++) как «законченные объекты». Учитывая массив, мы можем получить указатель на массив сразу после него, разыменовать этот указатель и использовать преобразование массива в указатель в результирующей ссылке на массив, чтобы получить указатель конца-конца для исходного массива:

#define END(array) (*(&array + 1))

// ...

int a [42];
do_something (a, END (a));

У меня вопрос такой: Разыменовывает ли указатель на несуществующий объект массива, этот код демонстрирует неопределенное поведение? Меня интересует, что говорят об этом коде самые последние версии C и C ++ (не потому, что я собираюсь его использовать, поскольку есть лучшие способы достижения того же результата, а потому, что это интересный вопрос).

7

Решение

Я использовал это в своем собственном коде, как (&arr)[1],

Я уверен, что это безопасно. Распад массива в указатель не является «преобразованием lvalue в rvalue», хотя он начинается с lvalue и заканчивается rvalue.

1

Другие решения

Это неопределенное поведение.

a имеет тип array of 42 int,

&a имеет тип pointer to array of 42 int, (Обратите внимание, что это не преобразование массива в указатель)

&a + 1 также имеет тип pointer to array of 42 int

5.7p5 заявляет:

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

Указатель не указывает на элемент объекта массива. Он указывает на объект массива. Таким образом, «в противном случае поведение не определено» верно. Поведение не определено.

1

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

Но основная идея использования &array + 1 правильно, всякий раз, когда array это значение. (Есть случаи, когда массивы не являются lvalues.) В этом случае это допустимая операция с указателем. Теперь, чтобы получить указатель на первый элемент, вам просто нужно привести его к базовому типу. В вашем случае это было бы

(int*)(&array + 1)

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

К сожалению, я не вижу способа сделать такой тип выражения независимым, чтобы вы могли поместить это в общий макрос, если вы не приведете к void*, (С gcc typeof расширение, которое вы могли бы сделать, например)
Так что вам лучше придерживаться портативного (array)+ARRAYLEN(array), что надо работать во всех случаях.

В странном угловом случае массив, который является частью struct и возвращается как rvalue из функции, не являющейся lvalue. Я думаю, что стандарт допускает арифметику указателей и здесь, но я никогда не понимал эту конструкцию полностью, поэтому я не уверен, что она будет работать в этом случае.

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector