В самом строгом смысле это не неопределенное поведение, а определяемое реализацией. Таким образом, хотя это нежелательно, если вы планируете поддерживать неосновные архитектуры, вы, вероятно, можете это сделать.
Стандартная цитата, заданная interjay, является хорошей и указывает на UB, но, на мой взгляд, это всего лишь второй лучший результат, поскольку он имеет дело с арифметикой указатель-указатель (как ни странно, один явно UB, а другой нет). В этом вопросе есть параграф, касающийся операции:
[expr.post.incr] / [expr.pre.incr]
Операндом должен быть [...] или указатель на полностью определенный тип объекта.
О, подождите, полностью определенный тип объекта? Это все? Я имею в виду, действительно, типа ? Так тебе вообще не нужен объект?
Чтобы найти подсказку о том, что что-то там, возможно, не так четко определено, требуется немало чтения. Потому что до сих пор он читается так, как будто вам совершенно разрешено это делать, никаких ограничений.
[basic.compound] 3
делает заявление о том, какой тип указателя может иметь, и, будучи не одним из трех других, результат вашей операции явно попадет под 3.4: неверный указатель .
Однако это не говорит о том, что вам не разрешено иметь неверный указатель. Напротив, в нем перечислены некоторые очень распространенные, нормальные условия (например, время окончания хранения), когда указатели регулярно становятся недействительными. Так что, по-видимому, это допустимо. И действительно:
[basic.stc] 4 Переадресация
через недопустимое значение указателя и передача недопустимого значения указателя в функцию освобождения имеют неопределенное поведение. Любое другое использование недопустимого значения указателя имеет поведение, определяемое реализацией.
Мы делаем «любое другое», так что это не неопределенное поведение, а определяемое реализацией, поэтому, как правило, допустимо (если реализация явно не говорит что-то другое).
К сожалению, это не конец истории. Хотя с этого момента чистый результат больше не меняется, он становится более запутанным, чем дольше вы ищете «указатель»:
[basic.compound]
Допустимое значение типа указателя объекта представляет собой адрес байта в памяти или нулевой указатель. Если объект типа T находится по адресу, то говорят, что [...] указывает на этот объект, независимо от того, как было получено значение .
[Примечание: Например, считается, что адрес, следующий за концом массива, указывает на несвязанный объект типа элемента массива, который может быть расположен по этому адресу. [...]].
Читайте как: ОК, кого это волнует! Пока указатель указывает где-то в памяти , я в порядке?
[basic.stc.dynamic.safety] Значение указателя является безопасно полученным указателем [бла-бла]
Читайте как: хорошо, безопасно выведено, что угодно. Это не объясняет, что это такое, и не говорит, что мне это действительно нужно. Безопасно-производные в-щеколду. Очевидно, у меня все еще могут быть указатели, не являющиеся безопасными, просто отлично. Я предполагаю, что разыменование их, вероятно, не будет хорошей идеей, но вполне допустимо иметь их. Это не говорит иначе.
Реализация может иметь ослабленную безопасность указателя, и в этом случае достоверность значения указателя не зависит от того, является ли оно безопасно полученным значением указателя.
О, так что это может не иметь значения, только то, что я думал. Но подождите ... "не может"? Значит, может и так . Откуда мне знать?
Альтернативно, реализация может иметь строгую безопасность указателя, и в этом случае значение указателя, которое не является безопасно полученным значением указателя, является недопустимым значением указателя, если только ссылка на полный объект не имеет длительности динамического хранения и ранее не была объявлена достижимой
Подождите, так что даже возможно, что мне нужно вызывать declare_reachable()
каждый указатель? Откуда мне знать?
Теперь вы можете преобразовать в intptr_t
, что является четко определенным, давая целочисленное представление безопасно полученного указателя. Для которого, конечно, являясь целым числом, вполне законно и четко определено увеличивать его по своему усмотрению.
И да, вы можете преобразовать intptr_t
обратно в указатель, который также четко определен. Просто, не будучи исходным значением, больше не гарантируется, что у вас есть безопасный производный указатель (очевидно). Тем не менее, в целом, к букве стандарта, будучи определяемой реализацией, это на 100% законно:
[expr.reinterpret.cast] 5
Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель. Указатель преобразуется в целое число достаточного размера [...] и обратно в исходное значение того же типа указателя [...]; в противном случае отображения между указателями и целыми числами определяются реализацией.
Подвох
Указатели - это обычные целые числа, только вы случайно используете их в качестве указателей. О, если бы только это было правдой!
К сожалению, существуют архитектуры, где это совсем не так, и простая генерация недопустимого указателя (не разыменование его, просто наличие его в регистре указателей) вызовет ловушку.
Так что это основа «реализация определена». Это и тот факт, что увеличение указателя в любое время, как вам угодно, может, конечно, вызвать переполнение, с которым стандарт не хочет иметь дело. Конечное адресное пространство приложения может не совпадать с местом переполнения, и вы даже не знаете, существует ли такая вещь, как переполнение для указателей в конкретной архитектуре. В общем, это кошмарный беспорядок, не имеющий никакого отношения к возможным выгодам.
Работать с условием «один объект в прошлом» с другой стороны легко: реализация должна просто убедиться, что ни один объект не был выделен, чтобы последний байт в адресном пространстве был занят. Так что это четко определено, поскольку полезно и тривиально гарантировать.