Где именно в стандарте C ++ говорится, что разыменование неинициализированного указателя является неопределенным поведением? - PullRequest
11 голосов
/ 26 ноября 2010

Пока я не могу найти, как сделать вывод, что следующее:

int* ptr;
*ptr = 0;

- неопределенное поведение.

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

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

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

Ответы [ 7 ]

12 голосов
/ 26 ноября 2010

Раздел 4.1 выглядит как кандидат ( выделение шахты ):

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

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

6 голосов
/ 16 декабря 2013

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

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

и

Недопустимый итератор - это итератор, который может быть единственным. 268

и сноска 268 гласит:

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

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

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

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

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

Обновление

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

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

и если мы перейдем к разделу 3.10 L-значения и r-значения абзац 1 говорит ( выделение мое ):

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

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

5 голосов
/ 13 декабря 2010

Вопрос ОП бессмыслица. Не требуется, чтобы в стандарте говорилось, что определенное поведение не определено, и я бы сказал, что все такие формулировки должны быть удалены из стандарта, потому что он вводит людей в заблуждение и делает стандарт более многословным, чем необходимо.

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

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

3 голосов
/ 08 июня 2015

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

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

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

Когда получено хранилище для объекта с автоматическим или динамическим сроком хранения, объект имеет неопределенное значение, и если для объекта не выполняется инициализация, этот объект сохраняет неопределенное значение, пока это значение не будет заменено.

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

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


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

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

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

3 голосов
/ 26 ноября 2010

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

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

3 голосов
/ 26 ноября 2010

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

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

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

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

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

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

1 голос
/ 08 июня 2015

Даже если в обычном хранилище чего-либо в памяти не было бы «места» для каких-либо битов прерываний или представлений прерываний, реализации не обязаны хранить автоматические переменные так же, как переменные статической длительности, за исключением случаев, когда существует вероятность, что пользователь код может содержать указатель на них где-то. Это поведение наиболее заметно с целочисленными типами. В типичной 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 содержит значение Indeterminate Value, не требуется, чтобы при ее чтении получалось значение в диапазоне uint16_t. Поскольку при использовании таких целых чисел без знака могут возникнуть непредвиденные ситуации, нет никаких оснований ожидать, что указатели не смогут вести себя еще хуже.

...