Подтвердить что ты не робот

Деление на ноль в постоянном выражении

Мой игрушечный компилятор сработает, если я делюсь на ноль в постоянном выражении:

int x = 1 / 0;

Является ли это поведение допустимым с помощью стандартов C и/или С++?

4b9b3361

Ответ 1

Простое присутствие 1 / 0 не позволяет компилятору сбой. В лучшем случае допускается предположить, что выражение никогда не будет оценено, и, таким образом, выполнение никогда не достигнет данной строки.

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

1/0 - только UB, если оценивается.

Стандарт C11 дает явный пример 1 / 0, определяемый поведением, когда он не оценивается:

Таким образом, в следующей инициализации

        static int i = 2 || 1 / 0;

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

Раздел 6.6, сноска 118.

1/0 не является постоянным выражением.

Раздел 6.6 стандарта C11, в разделе "Ограничения", говорится

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

Так как 1/0 не оценивает константу в диапазоне значений, представляемых int, 1/0 не является константным выражением. Это правило о том, что считается постоянным выражением, как правило о том, что у него нет назначений. Вы можете видеть, что по крайней мере для С++, Clang не считает 1/0 постоянным выражением:

prog.cc:3:18: error: constexpr variable 'x' must be initialized by a constant expression
   constexpr int x = 1/ 0 ;
                 ^   ~~~~

Для неопытного 1/0 не было бы смысла быть UB.

(x == 0) ? x : 1 / x отлично определен, даже если x равно 0, а оценка 1/x - UB. Если бы это было так, что (0 == 0) ? 0 : 1 / 0 были UB, это было бы нонсенсом.

Ответ 2

Да, деление на ноль - это поведение undefined, и ни стандарт C, ни С++ не налагает никаких требований в таких случаях. Хотя в этом случае я считаю, что вы должны хотя бы выдать диагностику (см. Ниже).

Прежде чем я начну ссылаться на стандарты, я должен отметить, что, хотя это может быть совместимым поведением, качество реализации - это другая проблема, а просто соответствие - это не то же самое, что полезно. Насколько я знаю, команды gcc, clang, Visual Studio и Intel (как tpg2114) считают внутренние ошибки компилятора (ICE) ошибками, о которых следует сообщать. Следует отметить, что как текущие gcc, так и clang вызывают предупреждение для этого случая, независимо от флагов. В случае, когда оба операнда являются литералами/константами, случай, который мы здесь имеем, кажется довольно прямым для обнаружения и обеспечения диагностики для этого. clang производит следующую диагностику для этого случая (видеть его в прямом эфире):

warning: division by zero is undefined [-Wdivision-by-zero]
int x = 1 / 0 ;
          ^ ~

Из проекта стандартного раздела C11 6.5.5 Мультипликативные операторы (выделение мое):

Результат оператора/является частным от деления первого операнда на второй; [...] , если значение второй операнд равен нулю, поведение undefined.

и поэтому это поведение undefined.

В стандартном разделе проекта С++ 5.6 [expr.mul] говорится:

Двоичный/оператор дает коэффициент [...] Если второй операнд/или% равен нулю, поведение undefined [...]

снова undefined.

Оба варианта стандарта С++ и черновик C имеют аналогичное определение для поведения undefined, говорящего как:

[...], для которого настоящий международный стандарт не требует требований

Фраза накладывает никаких требований, похоже, слишком допускает любое поведение, в том числе носовые демоны. Оба имеют аналогичную заметку, в которой говорится следующее:

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

Итак, хотя примечания не являются нормативными, кажется, что если вы собираетесь прекратить во время перевода, вы должны, по крайней мере, выпустить диагностику. Термин завершение не определен, поэтому трудно утверждать, что это позволяет. Я не думаю, что видел случай, когда clang и gcc имеют ICE без диагностики.

Должен ли выполняться код?

Если мы читаем Может ли код, который никогда не будет выполнен, вызывает поведение undefined?, мы можем видеть, по крайней мере, в случае C есть место для обсуждения, где 1 / 0 должен выполняться, чтобы вызвать поведение undefined. Что еще хуже в случае С++, определение поведения отсутствует, поэтому часть анализа, используемого для случая C, не может использоваться для случая С++.

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

С точки зрения C отчет о дефекте WG14 109 дополнительно разъясняет это. Приведен следующий пример кода:

int foo()
{
  int i;
  i = (p1 > p2); /* Must this be "successfully translated"? */
  1/0; /* Must this be "successfully translated"? */
  return 0;
} 

и ответ включал:

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

Итак, в случае C, если не будет гарантировано выполнение кода, вызывающего поведение undefined, тогда компилятор должен успешно перевести программу.

С++ constexpr case

Если x была константой constexpr:

constexpr int x = 1 / 0 ;

он будет плохо сформирован, и gcc произведет предупреждение, а clang сделает ошибку (увидеть его в прямом эфире):

error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1/ 0 ;
             ^   ~~~~
note: division by zero
constexpr int x = 1/ 0 ;
                  ^
warning: division by zero is undefined [-Wdivision-by-zero]
constexpr int x = 1/ 0 ;
                  ^ ~

Полезно отметить, что деление на ноль undefined.

Проект стандартного раздела С++ 5.19 Константные выражения [expr.const] говорят:

Условное выражение e является выражением постоянной константы, если оценка e, следуя правилам абстрактная машина (1.9), оценила бы одно из следующих выражений

и включает следующую марку:

операция, которая будет иметь поведение undefined [Примечание: в том числе, например, переполнение целых чисел со знаком (П. 5), определенную арифметику указателя (5.7), деление на ноль (5.6) или некоторые операции сдвига (5.8) -end note];

Является 1/0 постоянным выражением в C11

1 / 0 не является константным выражением в C11, мы можем видеть это из раздела 6.6 Константные выражения, которые гласят:

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

хотя он позволяет:

Реализация может принимать другие формы постоянных выражений.

Итак, 1 / 0 не является постоянным выражением в C или С++, но это не меняет ответ, поскольку он не используется в контексте, который требует постоянного выражения. Я подозреваю, что OP означает, что 1 / 0 доступен для постоянной сгибания, поскольку оба операнда являются литералами, это также объясняет крушение.

Ответ 3

Из стандартного черновика C (N1570):

6.5.5 Мультипликативные операторы

...

  1. Результатом оператора/является фактор от деления первого операнда на второй; результатом оператора% является остаток. В обеих операциях , если значение второй операнд равен нулю, поведение undefined.

И о undefined поведении в главе 3. Термины, определения и символы:

3.4.3

  • undefined поведение
    поведение, при использовании непереносимой или ошибочной программы или ошибочных данных, для которых настоящий международный стандарт не предъявляет никаких требований.
  • ПРИМЕЧАНИЕ. Возможно, поведение undefined варьируется от полного игнорирования ситуации с непредсказуемым результаты, вести себя во время трансляции или выполнение программы в документальном виде, характерном для (с выдачей диагностического сообщения или без него), для прекращения перевода или выполнение (с выдачей диагностического сообщения).

Таким образом, сбой компилятора разрешен.

Ответ 4

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

Функция оценки выражения компилятора My C принимает выражение в Reverse Polish Notation (массив значений (числа и идентификаторы) и операторы) и возвращает две вещи: флаг для того, выражает ли выражение константу и значение, если оно константа (0 в противном случае). Если результат является константой, весь RPN сводится к этой константе. 1/0 не является постоянным выражением, так как он не оценивает постоянное целочисленное значение. RPN не уменьшается на 1/0 и остается неповрежденным.

В C статические переменные могут быть инициализированы только с постоянными значениями. Таким образом, компилятор выдает ошибку, когда видит, что инициализатор для статической переменной не является константой. Переменные автоматического хранилища могут быть инициализированы с помощью неконстантных выражений. В этом случае мой компилятор генерирует код для оценки 1/0 (у него все еще есть RPN для этого выражения!). Если этот код достигнут во время выполнения, UB выполняется в соответствии с требованиями языка. [В x86 этот UB принимает форму деления на нулевое исключение CPU, тогда как в MIPS этот UB дает неверное значение частного (CPU не имеет деления на нулевое исключение).]

Мой компилятор правильно поддерживает короткое замыкание в выражениях -expressions и & -expressions. Таким образом, он оценивает 1 || 1/0 как 1 и 0 && 1/0 как 0, независимо от того, является ли правый операнд логического оператора константой. Функция оценки выражений удаляет правые операнды этих операторов (вместе с операторами), когда они не должны быть оценены и поэтому 1 || 1/0 преобразуется в 1 != 0 (напомним, что операнды && и || подвергаются сравнению с 0), что дает 1 и 0 && 1/0 преобразуется в 0 != 0, что дает 0.

Другим случаем, о котором стоит позаботиться, является INT_MIN / -1 и INT_MIN % -1 (для больших целых типов). Коэффициент не представляется в виде подписанного int (в случае целых 2-значных целых чисел, что у нас есть во всех современных ЦП), и поэтому это тоже UB (вы получаете то же деление за нулевое исключение на x86 во время выполнения), Аналогично я рассматриваю этот случай. Это выражение не может инициализировать переменную статического хранилища, и оно выбрасывается, если оно не оценивается в логическом & &/|| оператор. Он может инициализировать автоматическую переменную, что может привести к UB во время выполнения.

Я также выдаю предупреждение, когда такое разделение встречается.

Ответ 5

Как должен вести себя компилятор, не имеет отношения к значению выражения. Компилятор не должен терпеть крах. Период.

Я предполагаю, что педантичная реализация, учитывая такое выражение, скомпилируйте код, который будет исполнять 1/0 во время выполнения, но я не думаю, что будет рассматриваться как хорошая функция.

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