Стандарт, похоже, предполагает, что это не неопределенное поведение.
Соответствующий раздел стандарта выглядит следующим образом (о результате добавления типа указателя к целочисленному типу илинаоборот)
§5.7p4 [expr.add]
Когда выражение с целым типом добавляется или вычитается из указателя, результат имеет типоперанд указателя.Если операнд-указатель указывает на элемент объекта массива 84 , и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что различие индексов полученного и исходногоэлементы массива равны интегральному выражению.[...] выражение (P)+1
указывает один за последним элементом объекта массива.[...] Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, вычисление не должно производиться и переполняться;в противном случае поведение не определено.
С сноской 84:
С этой целью считается, что объект, который не является элементом массива, относится к одному элементному массиву;см. 5.3.1
(И §5.3.1 составляет около &
и *
)
Итак, для целей, где my_array_ptr
и my_past_end
указываютони указывают на my_array
, как если бы my_array
было на самом деле int[1][3]
.my_array_ptr
указывает на первый элемент (int[3]
, которым на самом деле является my_array).my_past_end
указывает на элемент «один за другим», и это хорошо определено.
Когда вы делаете *my_past_end
, вы создаете lvalue для int[3]
.Пока это не преобразуется в значение prvalue, вы фактически не обращаетесь к памяти, которая не является int[3]
, как если бы она была int[3]
.
§3.9.2p1 [basic.compound]
Составные типы можно создавать следующими способами:
[...]
4. ссылается на объекты или функции данного типа
§3.9.2p3 [basic.compound]
[...] [ Примечание : Например, адрес, следующий за концом массива (5.7) будет рассматриваться как указывающий на несвязанный объект типа элемента массива, который может быть расположен по этому адресу [...]
Обратите внимание, что он очень старается, чтобы убедиться, что-end-pointer по-прежнему определяется как адрес объекта.Поскольку ссылки могут ссылаться только на объекты, это допускает «недопустимые» ссылки, такие как *
(указатель конца конца), но по-прежнему запрещает нулевые ссылки, так как nullptr
не указывает на объект.
§4.2p1 [conv.array]
Значение l или значение типа "массив из N
T
" или "массив с неизвестной границей T
" можно преобразовать вprvalue типа "указатель на T
".Результатом является указатель на первый элемент массива.
Поскольку lvalue преобразуется, доступ к недопустимой памяти невозможен.Таким образом, во время преобразования создается значение типа int*
, указывающее на тот же адрес, что и &my_array[3]
, то есть значение std::end(my_array)
.Таким образом, они будут равны (что неудивительно, что указатели, указывающие на один и тот же адрес, определены как равные)
Вы также можете преобразовать my_past_end
в int*
напрямую, и это будет работать, как int[3]
является составным типом int
s (int
является подобъектом int[3]
), так что это будет менее запутанным способом сделать это.
В качестве примечания, причина, по которой &my_array[4]
работает как в C, так и в C ++, хотя C не имеет ссылок, потому что my_array[4]
определен как *(my_array + 4)
, а &my_array[4]
равен &*(my_array + 4)
, а &*(expression)
в C такой же, какпреобразование (expression)
в значение (и эффективное утверждение, что это ненулевой указатель).Поскольку в C ++ такого исключения не существует, здесь показана логика (my_array[4]
- это ссылка, которую нельзя преобразовать в значение).
Это выглядит довольно неоднозначно.«Несвязанные» объекты никогда больше не упоминаются в стандарте.Они могут быть заняты чем-то другим, например:
int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
arr[0][3]
и arr[1][0]
указывают на один и тот же адрес памяти.Но означает ли arr[0][3] = 10;
, что arr[1][0]
будет обновлено, чтобы прочитать 10
?
int test() {
int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
const int& i = arr[1][0];
arr[0][3] = 10;
return i;
}
Кажется, чтобы возвратить 10
в msvc и GCC (Оптимизация до mov eax, 10
ret
)
Поскольку ссылка ссылается на объект с адресом, &(reference)
четко определено.Но поскольку эти «несвязанные» объекты больше никогда не упоминаются, использование чего-либо, кроме их адреса, в силу буквального определения не является неопределенным поведением.