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

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

int x = 1 / 0;

Разрешено ли такое поведение стандартами C и / или C ++?

40

Решение

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

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

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

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

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

        static int i = 2 || 1 / 0;

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

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

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

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

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

Поскольку 1/0 не оценивается как константа в диапазоне значений, представляемых int, 1/0 не является константным выражением. Это правило о том, что считается константным выражением, например, о том, что в нем нет присваиваний. Вы можете видеть это по крайней мере для C ++, 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, это было бы ерунда.

22

Другие решения

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

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

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

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

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

и так это неопределенное поведение.

Проект стандартного раздела C ++ 5.6 [Expr.mul] говорит:

Бинарный / оператор дает частное […] Если второй операнд / или% равен нулю, поведение не определено […]

опять неопределенное поведение.

И проект стандарта C ++, и проект стандарта C имеют одинаковое определение неопределенного поведения, и оба говорят:

[…] для которого этот международный стандарт не предъявляет никаких требований

Фраза не предъявляет никаких требований Кажется, слишком позволяют любое поведение, в том числе носовые демоны. У обоих есть похожая заметка, в которой говорится что-то вроде:

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

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

Код должен быть выполнен?

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

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

С точки зрения C Отчет о дефектах WG14 109 далее разъясняет это. Ниже приведен пример кода:

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

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

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

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

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

C ++ 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 ;
^ ~

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

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

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

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

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

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

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

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

хотя, это позволяет:

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

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

40

Из стандарта C (N1570):

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

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

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

3.4.3

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

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

15

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

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

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

Мой компилятор правильно поддерживает короткие замыкания в выражениях || и &&-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 (то же самое для больших целых типов). Частное не представляется в виде целого числа со знаком (в случае целых чисел со знаком дополнения 2, что есть во всех современных процессорах), и поэтому это также UB (вы получаете то же самое деление на ноль исключение на x86 во время выполнения) , Я рассматриваю этот случай аналогично. Это выражение не может инициализировать переменную статического хранилища, и оно выбрасывается, если оно не вычисляется в логическом &&/ || оператор. Он может инициализировать автоматическую переменную, которая может привести к UB во время выполнения.

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

2

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

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

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

-1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector