Где, по сути, стандарт С++ разыменовывает неинициализированный указатель, это поведение undefined?

До сих пор я не могу найти, как вывести следующее:

int* ptr;
*ptr = 0;

- поведение undefined.

Прежде всего, там 5.3.1/1, в котором указано, что * означает косвенность, которая преобразует T* в T. Но это ничего не говорит о UB.

Затем часто цитируется 3.7.3.2/4, в котором говорится, что использование функции освобождения от ненулевого указателя делает указатель недействительным, а последующее использование недопустимого указателя - UB. Но в приведенном выше коде нет ничего об освобождении.

Как можно вывести UB в коде выше?

Ответ 1

Раздел 4.1 выглядит как кандидат (акцент мой):

Значение (3.10) нефункциональный, не массивный тип T может быть преобразуется в значение r. Если T - неполный тип, программа, которая требует такого преобразования плохо сформирован. Если объект, которому lvalue относится не к типу объекта T и не является объектом типа полученный от T, или , если объект неинициализированная, программа, которая требует такого преобразования undefined поведение. Если T является тип non-class, тип rvalue является cv-неквалифицированной версией T. В противном случае тип rvalue Т.

Я уверен, что просто поиск по "нетипичному" в спецификации может найти больше кандидатов.

Ответ 2

Я нашел ответ на этот вопрос - неожиданный угол проекта стандарта С++, раздел 24.2 Требования к итератору, в частности раздел 24.2.1 В общем параграфе 5 и 10, которые соответственно говорят (акцент мой):

[...] [Пример: после объявления неинициализированного указателя x (как и в случае с int * x;), x всегда должно быть принято, чтобы иметь сингулярное значение a указатель. -end example] [...] Выделяемые значения всегда неособые.

и

Недействительный итератор является итератором, который может быть сингулярным. 268

и сноска 268 говорится:

Это определение относится к указателям , поскольку указатели являются итераторами. Эффект разыменования итератора, который был аннулирован, составляет undefined.

Хотя похоже, что существует какой-либо противоречие, является ли нулевой указатель сингулярным или нет, и похоже, что термин сингулярное значение должно быть правильно определено в более общий способ.

Цель единственного числа, похоже, хорошо суммируется в отчете о дефекте 278. Что означает действительность итератора? в разделе обоснования, в котором говорится:

Почему мы говорим, что "может быть сингулярным" , а не "сингулярным"? Это потому, что действительный итератор - это тот, который, как известно, неособенный. Недействительность итератора означает его изменение таким образом, что он уже не считается неособым. Пример: вставка элемента в середину вектора корректно называется invalidate всех итераторов, указывающих на вектор. Это не обязательно означает, что все они становятся единственными.

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

Обновить

Альтернативный подход здравого смысла должен был бы отметить, что проект стандартного раздела 5.3.1 Унарные операторы, пункт 1, который гласит (акцент мой):

Оператор унарного * выполняет косвенное обращение: выражение, к которому оно применяется, должно быть указателем на тип объекта или указателем на тип функции, а результат - это lvalue, относящийся к объекту или функции, на которую указывает выражение. [...]

и если мы перейдем к разделу 3.10 Lvalues ​​и rvalues ​​в параграфе 1 (подчеркивает мой):

Значение l (так называемое, исторически, поскольку lvalues ​​может появиться в левой части выражения присваивания) обозначает функцию или объект. [...]

но ptr не будет, кроме случайного, указывать на действительный объект.

Ответ 3

Вопрос OP - это глупость. Нет требования, чтобы в Стандарте говорилось, что определенное поведение undefined, и действительно я бы сказал, что все такие формулировки будут удалены из Стандарта, потому что это смущает людей и делает Стандарт более подробным, чем необходимо.

Стандарт определяет определенное поведение. Вопрос в том, указывает ли это поведение в этом случае? Если это не так, поведение undefined независимо от того, говорит ли он так явно.

Фактически спецификация того, что некоторые вещи undefined оставлены в стандарте, прежде всего в качестве средства отладки для авторов стандартов, идея состоит в том, чтобы создать противоречие, если есть требование в одном месте, которое конфликтует с явным утверждением поведения undefined в другом: это способ доказать дефект в Стандарте. Без явной инструкции поведения undefined другое предписание, предписывающее поведение, было бы нормативным и неоспоримым.

Ответ 4

Я не собираюсь притворяться, что знаю об этом много, но некоторые компиляторы инициализируют указатель на NULL и разыменование указателя на NULL - это UB.

Также учитывая, что неинициализированный указатель мог указать на что угодно (это включает в себя NULL), вы могли бы заключить, что он UB, когда вы разыгрываете его.

Примечание в разделе 8.3.2 [dcl.ref]

[Примечание: в частности, нулевая ссылка не может существовать в четко определенном программы, потому что единственный способ создать такую ​​ссылку было бы привязать его к "объекту", полученному разыменования нулевого указателя, который вызывает поведение undefined. В виде описанный в 9.6, ссылка не может быть связанными непосредственно с битовым полем. ]

-ISO/IEC 14882: 1998 (E), стандарт ISO С++, в разделе 8.3.2 [dcl.ref]

Думаю, я должен был написать это как комментарий вместо этого, я действительно не уверен.

Ответ 5

Чтобы разыменовать указатель, вам нужно прочитать из переменной указателя (не говоря о объекте, на который он указывает). Чтение из неинициализированной переменной - это поведение undefined.

То, что вы делаете со значением указателя после того, как вы его прочитали, теперь не имеет значения, будь то запись (например, в вашем примере) или чтение с объекта, на который он указывает.

Ответ 6

Оценка неинициализированного указателя вызывает поведение undefined. Поскольку разыменование указателя сначала требует его оценки, это означает, что разыменование также вызывает поведение undefined.

Это было верно как в С++ 11, так и в С++ 14, хотя формулировка изменилась.

В С++ 14 он полностью покрыт [dcl.init]/12:

При получении хранилища для объекта с автоматической или динамической продолжительностью хранения объект имеет неопределенное значение, и если для объекта не выполняется инициализация, этот объект сохраняет неопределенное значение до тех пор, пока это значение не будет заменено.

Если неопределенное значение получается при оценке, поведение undefined, за исключением следующих случаев:

где "следующие случаи" являются конкретными операциями на unsigned char.


В С++ 11 [conv.lval/2] рассмотрел это в процедуре преобразования lvalue-to-rvalue (например, извлечение значения указателя из области хранения, обозначенной ptr):

Значение не-функционального типа без массива T может быть преобразовано в prvalue. Если T является неполным типом, программа, которая требует этого преобразования, плохо сформирована. Если объект, к которому относится glvalue, не является объект типа T и не является объектом типа, полученного из T, , или если объект не инициализирован, программа, которая требует, чтобы это преобразование имело поведение undefined.

Полужирная часть была удалена для С++ 14 и заменена дополнительным текстом в [dcl.init/12].

Ответ 7

Даже если нормальное хранение чего-либо в памяти не будет иметь "комнаты" для любых ловушек-ловушек или ловушек, реализациям не требуется сохранять автоматические переменные так же, как переменные статической продолжительности, за исключением случаев, когда есть вероятность, что пользователь код может содержать указатель на них где-нибудь. Это поведение наиболее заметно с целыми типами. В типичной 32-битной системе, учитывая код:

uint16_t foo(void);
uint16_t bar(void);
uint16_t blah(uint32_t q)
{
  uint16_t a;
  if (q & 1) a=foo();
  if (q & 2) a=bar();
  return a;
}
unsigned short test(void)
{
  return blah(65540);
}

было бы неудивительно, что для test было бы получено 65540, хотя это значение выходит за пределы представимого диапазона uint16_t, типа, который не имеет ловушечных представлений. Если локальная переменная типа uint16_t содержит неопределенное значение, нет требования, чтобы чтение давало значение в диапазоне uint16_t. Так как неожиданное поведение может возникать при использовании даже целых чисел без знака таким образом, нет оснований ожидать, что указатели не могут вести себя еще хуже.