AFAIK, хотя мы не можем создать массив статической памяти размером 0, но мы можем сделать это с динамическими:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Как я читал, p
действует как элемент «один конец». Я могу напечатать адрес, на который p
указывает.
if(p)
cout << p << endl;
Хотя я уверен, что мы не можем разыменовать этот указатель (last-last-element), как мы не можем использовать итераторы (past-last-element), но в чем я не уверен, является ли увеличение этого указателя
p
? Похоже ли неопределенное поведение (UB) с итераторами?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Итачи Учива
источник
источник
std::vector
с 0.begin()
уже равно,end()
поэтому вы не можете увеличить итератор, который указывает на начало.Ответы:
Указатели на элементы массивов могут указывать на действительный элемент или один за концом. Если вы увеличиваете указатель так, чтобы он проходил больше одного конца, поведение не определено.
Для вашего массива нулевого размера
p
он уже указывает на один конец, поэтому увеличение его не допускается.См. C ++ 17 8.7 / 4 относительно
+
оператора (++
имеет те же ограничения):источник
x[i]
такой же, какx[i + j]
и когда обаi
иj
имеют значение 0?x[i]
- это тот же элемент, какx[i+j]
если быj==0
.Я думаю, у вас уже есть ответ; Если вы посмотрите немного глубже: вы сказали, что инкрементным итератором является UB, таким образом: Этот ответ в том, что такое итератор?
Итератор - это просто объект, у которого есть указатель, и увеличивающий этот итератор действительно увеличивает указатель, который у него есть. Таким образом, во многих аспектах итератор обрабатывается в виде указателя.
Это из C ++ primer 5 издание Lipmann.
Так что это UB не делай этого.
источник
В самом строгом смысле это не неопределенное поведение, а определяемое реализацией. Таким образом, хотя это нежелательно, если вы планируете поддерживать неосновные архитектуры, вы, вероятно, можете это сделать.
Стандартная цитата, заданная interjay, является хорошей и указывает на UB, но, на мой взгляд, это всего лишь второй лучший результат, поскольку он имеет дело с арифметикой указатель-указатель (как ни странно, один явно UB, а другой нет). В этом вопросе есть параграф, касающийся операции:
О, подождите, полностью определенный тип объекта? Это все? Я имею в виду, действительно, типа ? Так тебе вообще не нужен объект?
Чтобы найти подсказку о том, что что-то там, возможно, не так четко определено, требуется немало чтения. Потому что до сих пор он читается так, как будто вам совершенно разрешено это делать, никаких ограничений.
[basic.compound] 3
делает заявление о том, какой тип указателя может иметь, и, будучи не одним из трех других, результат вашей операции явно попадет под 3.4: неверный указатель .Однако это не говорит о том, что вам не разрешено иметь неверный указатель. Напротив, в нем перечислены некоторые очень распространенные, нормальные условия (например, время окончания хранения), когда указатели регулярно становятся недействительными. Так что, по-видимому, это допустимо. И действительно:
Мы делаем «любое другое», так что это не неопределенное поведение, а определяемое реализацией, поэтому, как правило, допустимо (если реализация явно не говорит что-то другое).
К сожалению, это не конец истории. Хотя с этого момента чистый результат больше не меняется, он становится более запутанным, чем дольше вы ищете «указатель»:
Читайте как: ОК, кого это волнует! Пока указатель указывает где-то в памяти , я в порядке?
Читайте как: хорошо, безопасно выведено, что угодно. Это не объясняет, что это такое, и не говорит, что мне это действительно нужно. Безопасно-производные в-щеколду. Очевидно, у меня все еще могут быть указатели, не являющиеся безопасными, просто отлично. Я предполагаю, что разыменование их, вероятно, не будет хорошей идеей, но вполне допустимо иметь их. Это не говорит иначе.
О, так что это может не иметь значения, только то, что я думал. Но подождите ... "не может"? Значит, может и так . Откуда мне знать?
Подождите, так что даже возможно, что мне нужно вызывать
declare_reachable()
каждый указатель? Откуда мне знать?Теперь вы можете преобразовать в
intptr_t
, что является четко определенным, давая целочисленное представление безопасно полученного указателя. Для которого, конечно, являясь целым числом, вполне законно и четко определено увеличивать его по своему усмотрению.И да, вы можете преобразовать
intptr_t
обратно в указатель, который также четко определен. Просто, не будучи исходным значением, больше не гарантируется, что у вас есть безопасный производный указатель (очевидно). Тем не менее, в целом, к букве стандарта, будучи определяемой реализацией, это на 100% законно:Подвох
Указатели - это обычные целые числа, только вы случайно используете их в качестве указателей. О, если бы только это было правдой!
К сожалению, существуют архитектуры, где это совсем не так, и простая генерация недопустимого указателя (не разыменование его, просто наличие его в регистре указателей) вызовет ловушку.
Так что это основа «реализация определена». Это и тот факт, что увеличение указателя в любое время, как вам угодно, может, конечно, вызвать переполнение, с которым стандарт не хочет иметь дело. Конечное адресное пространство приложения может не совпадать с местом переполнения, и вы даже не знаете, существует ли такая вещь, как переполнение для указателей в конкретной архитектуре. В общем, это кошмарный беспорядок, не имеющий никакого отношения к возможным выгодам.
Работать с условием «один объект в прошлом» с другой стороны легко: реализация должна просто убедиться, что ни один объект не был выделен, чтобы последний байт в адресном пространстве был занят. Так что это четко определено, поскольку полезно и тривиально гарантировать.
источник
+
оператор (из которого происходит++
поток), что означает, что указание после «один за концом» не определено.