Изменен ли стандарт С++ в отношении использования неопределенных значений и поведения undefined в С++ 14?

Как описано в разделе "Включает ли инициализация преобразование lvalue в rvalue"? Является ли int x = x; UB? Стандарт C++ содержит удивительный пример в разделе 3.3.2 Точка объявления, в которой int инициализируется своим собственным неопределенным значением:

int x = 12;
{ int x = x; }

Здесь второй x инициализируется своим собственным (неопределенным) значением. - конец примера]

То, на что Йоханнес отвечает на этот вопрос, является неопределенным поведением, так как требует преобразования lvalue в rvalue.

В последнем C++ 14 проекте стандарта N3936 который можно найти здесь, этот пример был изменен на:

unsigned char x = 12;
{ unsigned char x = x; }

Здесь второй x инициализируется своим собственным (неопределенным) значением. - конец примера]

Изменилось ли что-то в C++ 14 в отношении неопределенных значений и неопределенного поведения, которое послужило причиной этого изменения в примере?

Ответ 1

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

Отчет о дефекте 1787, предлагаемый текст которого можно найти в N3914 1, был недавно принят в 2014 году и включен в последний рабочий проект N3936:

Наиболее интересное изменение в отношении неопределенных значений было бы в разделе 8.5 параграфа 12, который идет от:

Если для объекта не указан инициализатор, объект инициализируется по умолчанию; если инициализация не выполняется, объект с автоматическим или динамическим сроком хранения имеет неопределенное значение. [Примечание: объекты со статическим или потоковым хранением инициализируются нулями, см. 3.6.2. - конец примечания]

чтобы (выделение мое):

Если для объекта не указан инициализатор, объект инициализируется по умолчанию. При получении хранилища для объекта с автоматическим или динамическим сроком хранения объект имеет неопределенное значение, и если для объекта не выполняется инициализация, этот объект сохраняет неопределенное значение до тех пор, пока это значение не будет заменено (5.17 [expr.ass]), [Примечание: объекты со статическим или потоковым хранилищем инициализируются нулями, см. 3.6.2 [basic.start.init]. - примечание] Если в результате оценки получено неопределенное значение, поведение не определено, за исключением следующих случаев:

  • Если неопределенное значение беззнакового типа узкого символа (3.9.1 [basic.fundamental]) получается путем оценки:

    • второй или третий операнд условного выражения (5.16 [expr.cond]),

    • правый операнд запятой (5.18 [expr.comma]),

    • операнд приведения или преобразования в беззнаковый узкий символьный тип (4.7 [conv.integral], 5.2.3 [expr.type.conv], 5.2.9 [expr.static.cast], 5.4 [expr.cast]), или же

    • выражение отброшенного значения (раздел 5 [expr]),

    тогда результат операции является неопределенным значением.

  • Если неопределенное значение типа узких символов без знака (3.9.1 [basic.fundamental]) создается путем вычисления правого операнда простого оператора присваивания (5.17 [expr.ass]), первый операнд которого является lvalue узкого знака без знака тип символа, неопределенное значение заменяет значение объекта, на которое ссылается левый операнд.

  • Если неопределенное значение узкого символьного типа без знака (3.9.1 [basic.fundamental]) создается путем вычисления выражения инициализации при инициализации объекта узкого символьного типа без знака, этот объект инициализируется неопределенным значением.

и включил следующий пример:

[ Пример:

int f(bool b) {
  unsigned char c;
  unsigned char d = c; // OK, d has an indeterminate value
  int e = d;           // undefined behavior
  return b ? d : 0;    // undefined behavior if b is true
}

- конец примера]

Мы можем найти этот текст в N3936, который является текущим рабочим проектом, а N3937 - C++14 DIS.

До C++ 1 год

Интересно отметить, что до этого проекта в отличие от C, который всегда имел четко определенное представление о том, какие использования неопределенных значений были неопределенными C++ использовал термин неопределенное значение, даже не определяя его (предполагая, что мы не можем заимствовать определение из C99) а также см. отчет о дефекте 616. Мы должны были полагаться на недостаточно конкретное преобразование lvalue-в-значение, которое в проекте стандарта C++ 11 описано в разделе 4.1 в пункте 1 преобразования Lvalue-в-значение, в котором говорится:

[...] если объект не инициализирован, программа, которая требует этого преобразования, имеет неопределенное поведение. [...]


Примечания:

  1. 1787 - это версия отчета о дефекте 616, эту информацию можно найти в N3903

Ответ 2

Хотя это правда, это не обоснование для этого правила. В частности, всем целочисленным типам без знака косвенно (по правилам арифметики по модулю) запрещено иметь представление ловушек. Но только неподписанные узкие типы символов попадают в это специальное исключение