В 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++? этот код (не потому, что я собираюсь его использовать, так как есть лучшие способы достижения того же результата, а потому, что это интересный вопрос).
6.5.6/8
. В С++ это5.7/5
. Если вам интересно, вот ссылка на средство проверки различий. - person   schedule 14.12.2013int[42]
является единственным элементом массива типаint[1][42]
).6.5.8
явно запрещает разыменование указателя за концом (в оцениваемом контексте), такого как указатель, образованный&array + 1
, который разыменовывается. - person Stuart Olsen   schedule 14.12.2013*ptr
появляется вне выраженияsizeof
/alignof
/_Alignof
). Если результат указывает на один после последнего элемента объекта массива, он не должен использоваться в качестве операнда оцениваемого унарного оператора*
-6.5.6.8
, N1570. - person Stuart Olsen   schedule 14.12.2013int* p = NULL; *p;
недействителен, по крайней мере, в C. - person Stuart Olsen   schedule 14.12.2013int a [1]; a [1];
6.5.6.8 в C определенно делает это незаконным, независимо от отсутствия преобразования lvalue-rvalue. Ради согласованности это должно сделать*NULL
незаконным, но если UB для унарного*
вообще определяется преобразованием lvalue-rvalue, я не уверен, что это так, потому что в этом выражении такого преобразования нет. Однако это выходит за рамки вопроса. - person Stuart Olsen   schedule 14.12.2013*p;
и говорится, что он не запускает преобразование в rvalue, поэтому нет неопределенного поведения. Так что мой предыдущий комментарий был ошибочным. (Тогда не уверен, как это работает, чтобы принудительно читать через изменчивый указатель.) - person Ben Voigt   schedule 14.12.2013(&array[1])
создавало бы так называемое пустое lvalue, а преобразование массива в указатель не вызывало бы предложение UB. К сожалению, похоже, что он еще не вышел из стадии разработки; N3797 не включает предложенную формулировку. - person Stuart Olsen   schedule 14.12.2013&*p
не является операцией), заявляют, что C ++ уже обрабатывает это, потому что создание lvalue путем разыменования недопустимого указателя не является UB, только преобразование lvalue-rvalue для таких lvalue есть. - person Ben Voigt   schedule 14.12.2013