在常量表达式中除以零

Dividing by zero in a constant expression

如果我在常量表达式中除以零,我的玩具编译器就会崩溃:

int x = 1 / 0;

C and/or C++ 标准允许这种行为吗?

来自 C 标准草案 (N1570):

6.5.5 Multiplicative operators

...

  1. The result of the / operator is the quotient from the division of the first operand by the second; the result of the % operator is the remainder. In both operations, if the value of the second operand is zero, the behavior is undefined.

以及第 3 章中关于未定义行为的术语、定义和符号:

3.4.3

  1. undefined behavior
    behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
  2. NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

所以编译器崩溃是允许的。

是的,除以零是未定义的行为,C 和 C++ 标准均未对此类情况施加任何要求。尽管在这种情况下我认为您至少应该发出诊断(见下文)。

在我引用标准之前,我应该注意,虽然这可能是符合标准的行为实施质量是一个不同的问题,仅仅符合标准并不等同于有用.据我所知,gcc、clang、Visual Studio 和英特尔(根据 tpg2114)团队考虑内部编译器错误(ICEs)是应该报告的错误。应该注意的是,无论提供的标志如何,当前的 gcc 和 clang 都会针对这种情况产生警告。在两个操作数都是 literals/constants 的情况下,在我们这里遇到的情况下,检测并为此提供诊断似乎相当直接。 clang 为这种情况生成以下诊断 (see it live):

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

来自 C11 标准草案6.5.5乘法运算符(强调我的):

The result of the / operator is the quotient from the division of the first operand by the second; [...] if the value of the second operand is zero, the behavior is undefined.

所以这是未定义的行为。

C++ 标准草案部分 5.6 [expr.mul] 说:

The binary / operator yields the quotient [...] If the second operand of / or % is zero the behavior is undefined [...]

又是未定义的行为。

C++ 标准草案和 C 标准草案对未定义行为都有相似的定义,都说:

[...]for which this International Standard imposes no requirements

短语 没有强加要求 似乎也允许任何行为,包括 nasal demons。两者都有类似的注释,内容大致如下:

Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

所以虽然注释不是规范的,但似乎如果你要在翻译过程中终止,你至少应该发出一个诊断。术语 terminating 没有定义,所以很难争论这允许什么。我不认为我见过 clang 和 gcc 有没有诊断的 ICE 的情况。

是否必须执行代码?

如果我们阅读 Can code that will never be executed invoke undefined behavior?,我们至少可以看到在 C 的情况下,存在争论的空间,其中必须执行 1 / 0 才能调用未定义的行为。更糟糕的是,在 C++ 案例中 行为 的定义不存在,因此用于 C 案例的部分分析不能用于 C++ 案例。

似乎如果编译器可以证明代码永远不会被执行那么我们可以推断它会是好像程序没有未定义的行为但我不知道不认为这是可以证明的,只是合理的行为。

从 C 的角度来看 WG14 defect report 109 进一步阐明了这一点。给出以下代码示例:

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

响应包括:

Furthermore, if every possible execution of a given program would result in undefined behavior, the given program is not strictly conforming.
A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior. Because foo might never be called, the example given must be successfully translated by a conforming implementation.

所以在C的情况下,除非可以保证调用未定义行为的代码将被执行,否则编译器必须成功翻译程序。

C++ constexpr 大小写

如果 x 是一个 constexpr 变量:

constexpr int x = 1 / 0 ;

它的格式不正确,gcc 会发出警告,而 clang 会使其出错 (see it live):

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] 说:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions

并包括以下项目符号:

an operation that would have undefined behavior [Note: including, for example, signed integer overflow (Clause 5), certain pointer arithmetic (5.7), division by zero (5.6), or certain shift operations (5.8) —end note ];

1/0是C11中的常量表达式

1 / 0 不是 C11 中的常量表达式,我们可以从 6.6 常量表达式部分看到:

Each constant expression shall evaluate to a constant that is in the range of representable values for its type.

尽管如此,它确实允许:

An implementation may accept other forms of constant expressions.

所以 1 / 0 在 C 或 C++ 中都不是常量表达式,但这不会改变答案,因为它没有在需要常量表达式的上下文中使用。我怀疑 OP 意味着 1 / 0 可用于常量折叠,因为两个操作数都是文字,这也可以解释崩溃。

仅仅 1 / 0 的存在不允许编译器崩溃。至多,允许假设表达式永远不会被计算,因此,执行永远不会到达给定的行。

如果表达式保证被求值,则标准对程序或编译器没有任何要求。 然后 编译器可能会崩溃。

1 / 0 仅在评估时为 UB。

C11 标准给出了 explicit example1 / 0 在未计算时定义的行为:

Thus, in the following initialization,

        static int i = 2 || 1 / 0;

the expression is a valid integer constant expression with value one.

第 6.6 节,脚注 118。

1 / 0 不是常量表达式。

C11 标准的

Section 6.6,在约束下,说

  1. Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.
  2. Each constant expression shall evaluate to a constant that is in the range of representable values for its type.

由于 1/0 的计算结果不是 int 可表示的值范围内的常量,因此 1/0 不是常量表达式。这是关于什么算作常量表达式的规则,就像关于其中没有赋值的规则。你可以看到至少对于 C++,Clang doesn't consider 1/0 a constant expression:

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,那就是废话了。

编译器的行为方式与表达式的值无关。编译器不应崩溃。时期。

我想象一个迂腐的实现,给定这样一个表达式,会 编译为将在 运行 时间执行 1/0 的代码,但我不认为 将被视为一个很好的功能。

所以剩下的space就是编译器应该拒绝编译它,并且 将其视为一些 class 源代码错误。

其他人已经提到了标准中的相关文本,因此,我不再重复。

我的 C 编译器的表达式求值函数采用反波兰表示法(值(数字和标识符)和运算符的数组)和 returns 两件事:一个标志,表示表达式是否求值为常量和值,如果它是常量(否则为 0)。如果结果是一个常数,则整个 RPN 将减少到该常数。 1/0 不是常量表达式,因为它的计算结果不是常量整数值。 RPN 没有减少 1/0 并保持不变。

在 C 中,静态变量只能用常量值初始化。因此,编译器在发现静态变量的初始值设定项不是常量时会出错。自动存储的变量可以用非常量表达式初始化。在这种情况下,我的编译器生成代码来评估 1/0(它仍然具有此表达式的 RPN!)。如果在运行时达到此代码,则 UB 会按照语言标准的规定发生。 [在 x86 上,此 UB 采用被零除 CPU 异常的形式,而在 MIPS 上,此 UB 产生不正确的商值(CPU 没有被零除异常)。]

我的编译器正确支持 || 表达式和 && 表达式中的短路。因此,它将 1 || 1/0 评估为 1,将 0 && 1/0 评估为 0,无论逻辑运算符的右侧操作数是否为常量。当不能对这些运算符(连同运算符)进行求值时,表达式求值函数会移除这些运算符的右侧操作数,因此 1 || 1/0 转换为 1 != 0(回想一下 && 和 || 的操作数经过与 0 比较),产生 1,0 && 1/0 转换为 0 != 0,产生 0.

另一种需要注意的情况是 INT_MIN / -1INT_MIN % -1(对于较大的整数类型也是如此)。商不能表示为有符号整数(在 2 的补码有符号整数的情况下,这是我们在所有现代 CPUs 中所拥有的)所以这也是 UB(你得到相同的除以零异常在运行时在 x86 上)。我以类似的方式处理这种情况。该表达式无法初始化静态存储变量,如果未在逻辑 &&/|| 中求值,它将被丢弃。操作员。它可以初始化一个自动变量,可能在运行时导致UB。

遇到这种划分我也发出警告