用常量expression式除以零
我的玩具编译器崩溃,如果我用一个常量expression式除以零:
int x = 1 / 0;
C和/或C ++标准允许这种行为吗?
仅仅存在1/0不允许编译器崩溃。 最多可以假定expression式永远不会被评估,因此执行永远不会到达给定的行。
如果保证expression式被评估,则该标准对程序或编译器没有要求。 然后编译器可能会崩溃。
如果评估,1/0仅为UB。
C11标准给出了一个明确的例子 ,当未评估时, 1 / 0
被定义为行为:
因此,在下面的初始化中,
static int i = 2 || 1 / 0;
该expression式是一个有效的整数常量expression式,值为1。
第6.6节,脚注118。
1/0不是一个常量expression式。
约束下的C11标准第6.6节说
- 常量expression式不得包含赋值,增量,减量,函数调用或逗号运算符,除非它们包含在未评估的子expression式中。
- 每个常量expression式都应该计算出一个常量,这个常量在它的types的可表示值范围内。
由于1/0不是以int表示的值范围内的常量,所以1/0不是常量expression式。 这是关于什么算作常量expression式的规则,就像关于没有赋值的规则一样。 你可以看到,至less对于C ++来说, Clang并不认为1/0是一个常量expression式 :
prog.cc:3:18: error: constexpr variable 'x' must be initialized by a constant expression constexpr int x = 1/ 0 ; ^ ~~~~
UB没有被评估的1/0是没有意义的。
(x == 0) ? x : 1 / x
(x == 0) ? x : 1 / x
是完全明确的,即使x是0并且评估1 / x是UB。 如果是(0 == 0) ? 0 : 1 / 0
(0 == 0) ? 0 : 1 / 0
1/0是UB,那将是无稽之谈。
是的,被零除是未定义的行为,在这种情况下C和C ++标准都没有规定任何要求。 虽然在这种情况下,我相信你至less应该发布诊断( 见下文 )。
在我引用这些标准之前,我应该注意到,虽然这可能是一致的行为,但是实施的质量是一个不同的问题,仅仅符合就不一样了。 据我所知,gcc,clang,Visual Studio和Intel( 根据tpg2114 )团队认为内部编译器错误( ICE )是应该报告的错误。 应该指出的是,无论提供哪个标志,当前的gcc和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标准草案对于未定义的行为都有类似的定义:
本国际标准对此没有要求
这句话似乎不允许任何行为,包括恶魔 。 两者都有类似的说法,沿着这样的方向说:
当本标准忽略任何明确的行为定义或程序使用错误的结构或错误的数据时,可能会出现未定义的行为。 允许的未定义的行为范围从完全忽略情况,以不可预知的结果 ,在翻译或程序执行期间以文档化的方式performance环境特征(不论是否发布诊断消息), 终止翻译或执行(发行的诊断消息) 。
所以,尽pipe笔记不是规范的,但是如果在翻译过程中要终止的话,至less应该发布一个诊断。 术语“ 终止”没有定义,所以很难说这是什么意思。 我不认为我曾经见过一个情况,即clang和gcc有没有诊断的ICE。
代码是否必须执行?
如果我们阅读可以永远不会执行的代码调用未定义的行为? 我们至less在C的情况下可以看出,为了调用未定义的行为,必须执行1/0。 在C ++的情况下更糟糕的是行为的定义不存在,所以用于C case的分析的一部分不能用于C ++的情况。
看起来,如果编译器能够certificate代码永远不会被执行,那么我们可以推断, 如果程序没有未定义的行为,但我不认为这是可certificate的,只是合理的行为。
从C的angular度来看, 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案例
如果x
是一个constexprvariables:
constexpr int x = 1 / 0 ;
这将是格式不正确,海湾合作委员会产生一个警告和铿锵使它错误( 看到它住 ):
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
节常量expression式[expr.const]说:
条件expression式e是一个核心常量expression式,除非按照抽象机器(1.9)的规则对e进行评估,否则将评估下列expression式之一
并包含以下内容:
(注:包括例如有符号整数溢出(第5章),某些指针算术(5.7),除以零(5.6)或某些移位操作(5.8) – 结束注释]。
1/0是C11中的一个常量expression式
1/0在C11中不是一个常量expression式,我们可以从6.6
节看到这个常量expression式,它表示:
每个常量expression式都应该计算出一个常量,这个常量在它的types的可表示值范围内。
虽然,它确实允许:
一个实现可能接受其他forms的常量expression式。
所以1/0在C或C ++中不是一个常量expression式,但是它不会改变答案,因为它没有被用在需要一个常量expression式的上下文中。 我怀疑OP意味着1/0可用于常量折叠,因为两个操作数都是文字,这也可以解释崩溃。
从C标准草案(N1570):
6.5.5乘法运算符
…
- /运算符的结果是第一个操作数除以第二个操作数的商。 %运算符的结果是余数。 在这两个操作中, 如果第二个操作数的值为零,则行为是未定义的。
关于第3章中未定义的行为。术语,定义和符号:
3.4.3
- 未定义的行为
行为,在使用不可移植或错误的程序结构或错误的数据时,本国际标准对此没有要求- 注意可能存在的未定义的行为范围从忽略完全不可预知的结果, 在翻译或程序执行过程中以环境特征(有或没有发布诊断消息) ,终止翻译或执行发出诊断消息)。
所以崩溃的编译器是允许的。
其他人已经从标准中提到了相关的文字,所以我不打算再重复一遍。
我的C编译器的expression式求值函数采用Reverse Polish Notation(数组和数组)的expression式(数组和数组),并返回两个值:expression式是否计算为常量的标志,以及是否为常量否则为0)。 如果结果是一个常数,那么整个RPN就会降低到这个常数。 1/0不是一个常量expression式,因为它不计算为一个常量整数值。 RPN在1/0时不减less,保持不变。
在C中,静态variables只能用常量初始化。 所以,当编译器看到一个静态variables的初始化器不是一个常量时就会出错。 自动存储的variables可以用非常量expression式初始化。 在这种情况下,我的编译器生成代码来评估1/0(它仍然有这个expression式的RPN!)。 如果在运行时达到这个代码,UB按照语言标准的规定发生。 [在x86上,这个UB采用零CPUexception除法的forms,而在MIPS上,这个UB产生一个不正确的商值(CPU没有被除数为零的例外)。]
我的编译器正确支持||expression式和&& – expression式中的短路。 所以,它评估1 || 1/0
1 || 1/0
为0 && 1/0
为0,无论逻辑运算符的右侧操作数是否为常量。 expression式评估函数在不必评估的情况下删除这些运算符的右手操作数(以及运算符),因此1 || 1/0
1 || 1/0
变换为1 != 0
(回忆&&和||的操作数与0进行比较),得到1和0 && 1/0
变换为0 != 0
,得到0。
另一个要注意的情况是INT_MIN / -1
和INT_MIN % -1
(同样适用于较大的整数types)。 商不能表示为一个有符号整数(在2的补码有符号整数的情况下,这是我们在所有现代的CPU),所以这也是UB(在x86运行时,你得到相同的除以零exception) 。 我同样处理这个案子。 这个expression式不能初始化一个静态存储的variables,如果在逻辑&& / ||中没有计算,它就会被抛弃 运营商。 它可以初始化一个自动variables,可能在运行时导致UB。
遇到这种情况时,我也会发出警告。
编译器应该如何performance与expression式的价值无关。 编译器不应该崩溃。 期。
我想象一个迂腐的实现,如果有这样一个expression式,就会编译成在运行时会执行1/0的代码,但是我不认为这是一个好的特性。
所以剩下的空间就是编译器应该拒绝编译它,并将其视为某类源代码错误。