В 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 ++ (не потому, что я собираюсь его использовать, поскольку есть лучшие способы достижения того же результата, а потому, что это интересный вопрос).
Я использовал это в своем собственном коде, как (&arr)[1]
,
Я уверен, что это безопасно. Распад массива в указатель не является «преобразованием lvalue в rvalue», хотя он начинается с lvalue и заканчивается rvalue.
Это неопределенное поведение.
a
имеет тип array of 42 int
,
&a
имеет тип pointer to array of 42 int
, (Обратите внимание, что это не преобразование массива в указатель)
&a + 1
также имеет тип pointer to array of 42 int
5.7p5 заявляет:
Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд-указатель указывает на элемент объекта массива, а […] в противном случае, поведение не определено
Указатель не указывает на элемент объекта массива. Он указывает на объект массива. Таким образом, «в противном случае поведение не определено» верно. Поведение не определено.
Это неопределенное поведение в C, разыменование указателя, который указывает за пределы существующего объекта, всегда, если он сам не является частью большего объекта, который содержит больше элементов.
Но основная идея использования &array + 1
правильно, всякий раз, когда array
это значение. (Есть случаи, когда массивы не являются lvalues.) В этом случае это допустимая операция с указателем. Теперь, чтобы получить указатель на первый элемент, вам просто нужно привести его к базовому типу. В вашем случае это было бы
(int*)(&array + 1)
Значение указателя на массив гарантированно совпадает со значением указателя на его первый элемент, различаются только типы.
К сожалению, я не вижу способа сделать такой тип выражения независимым, чтобы вы могли поместить это в общий макрос, если вы не приведете к void*
, (С gcc typeof
расширение, которое вы могли бы сделать, например)
Так что вам лучше придерживаться портативного (array)+ARRAYLEN(array)
, что надо работать во всех случаях.
В странном угловом случае массив, который является частью struct
и возвращается как rvalue из функции, не являющейся lvalue. Я думаю, что стандарт допускает арифметику указателей и здесь, но я никогда не понимал эту конструкцию полностью, поэтому я не уверен, что она будет работать в этом случае.