c++ - Инкремент указателя на динамический массив размером 0 не определен?




pointers undefined-behavior (2)

В самом строгом смысле это не неопределенное поведение, а определяемое реализацией. Таким образом, хотя это нежелательно, если вы планируете поддерживать неосновные архитектуры, вы, вероятно, можете это сделать.

Стандартная цитата, заданная 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
Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель. Указатель преобразуется в целое число достаточного размера [...] и обратно в исходное значение того же типа указателя [...]; Отображения между указателями и целыми числами определяются реализацией.

Подвох

Указатели - это обычные целые числа, только вы случайно используете их в качестве указателей. О, если бы только это было правдой!
К сожалению, существуют архитектуры, где это совсем не так, и простая генерация недопустимого указателя (не разыменование его, просто наличие его в регистре указателей) вызовет ловушку.

Так что это основа «реализация определена». Это и тот факт, что увеличение указателя в любое время, как вам угодно, может, конечно, вызвать переполнение, с которым стандарт не хочет иметь дело. Конечное адресное пространство приложения может не совпадать с местом переполнения, и вы даже не знаете, существует ли такая вещь, как переполнение для указателей в конкретной архитектуре. В общем, это кошмарный беспорядок, не имеющий никакого отношения к возможным выгодам.

Работать с условием «один объект в прошлом» с другой стороны легко: реализация должна просто убедиться, что ни один объект не был выделен, чтобы последний байт в адресном пространстве был занят. Так что это четко определено, поскольку полезно и тривиально гарантировать.

AFAIK, хотя мы не можем создать массив статической памяти размером 0, но мы можем сделать это с динамическими:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

Как я прочитал, p действует как элемент «один конец». Я могу напечатать адрес, на который указывает p .

if(p)
    cout << p << endl;
  • Хотя я уверен, что мы не можем разыменовать этот указатель (past-last-element), как мы не можем использовать итераторы (past-last-element), но в чем я не уверен, является ли увеличение этого указателя p ? Похоже ли неопределенное поведение (UB) с итераторами?

    p++; // UB?

Указатели на элементы массивов могут указывать на действительный элемент или один за концом. Если вы увеличиваете указатель так, чтобы он проходил больше одного конца, поведение не определено.

Для вашего массива нулевого размера p уже указывает на один конец, поэтому его увеличение не допускается.

См. C ++ 17 8.7 / 4 относительно оператора + ( ++ имеет те же ограничения):

Если выражение P указывает на элемент x[i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j ) указывают на (возможно, гипотетический) элемент x[i+j] если 0≤i + j≤n; в противном случае поведение не определено.







dynamic-arrays