在常量表达式中除以零

时间:2015-11-25 12:06:01

标签: c++ c divide-by-zero compile-time-constant constant-expression

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

int x = 1 / 0;

C和/或C ++标准是否允许这种行为?

5 个答案:

答案 0 :(得分:40)

是的,除以零是未定义的行为,在这种情况下C和C ++标准都没有强加任何要求。虽然在这种情况下我认为你至少应该发布一个诊断(见下面的)。

在我引用标准之前,我应该注意,虽然这可能是符合行为实施质量是一个不同的问题,但仅仅符合要求并不是有用的。据我所知,gcc,clang,Visual Studio和Intel(按照tpg2114 )团队认为内部编译器错误( ICEs )是应该报告的错误。应该注意的是,无论提供的标志如何,当前的gcc和clang都会对这种情况发出警告。在两个操作数都是文字/常量的情况下,我们在这里的情况,似乎很容易检测并为此提供诊断。 clang为此案例生成以下诊断( see it live ):

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

从草案C11标准部分6.5.5乘法运算符(强调我的):

  

/运算符的结果是来自第一个操作数的除法的商   第二; [...] 如果值为   第二个操作数为零,行为未定义。

所以它是未定义的行为。

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

  

二元/运算符产生商[...] 如果/或%的第二个操作数为零,则行为未定义 [...]

再次未定义的行为。

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

  

[...]本国际标准没有要求

短语没有要求似乎也允许任何行为,包括nasal demons。两者都有类似的说法,说的是:

  

当本国际标准忽略任何明确的定义时,可能会出现未定义的行为   行为或程序使用错误的构造或错误数据时。 允许的未定义行为   范围从完全忽略情况与不可预测的结果,到翻译期间的行为或   程序以环境特征的文件形式执行(有或没有发行   诊断消息),终止翻译或执行(发布诊断消息)

因此,虽然注释不是规范性的,但似乎如果您要在翻译期间终止,您至少应该发布诊断。术语终止未定义,因此很难说这是允许的。我不认为我见过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;
} 

并且回应包括:

  

此外,如果给定程序的每个可能执行都会导致未定义的行为,则给定的程序不严格符合。   
一致的实现必须简单地转换严格符合的程序,因为某个可能的程序执行会导致不确定的行为。因为foo可能永远不会被调用,所以给定的示例必须通过一致的实现成功翻译。

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

C ++ constexpr案例

如果x是constexpr变量:

constexpr int x = 1 / 0 ;

它会形成错误,gcc会发出警告,并且铿锵声会导致错误( 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]说:

  

条件表达式e是核心常量表达式,除非e的评估遵循规则   抽象机器(1.9),将评估以下表达式之一

并包含以下项目符号:

  

具有未定义行为的操作[注意:包括例如有符号整数溢出   (第5条),某些指针算术(5.7),除零(5.6)或某些换档操作(5.8)    - 注意];

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

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

  

每个常量表达式应计算为可表示范围内的常量   其类型的值。

尽管如此,它确实允许:

  

实现可以接受其他形式的常量表达式。

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

答案 1 :(得分:23)

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

如果保证表达式被评估,则该标准不对程序或编译器施加任何要求。 然后编译器可能会崩溃。

如果进行评估,1/0仅为UB。

C11标准在未评估时提供explicit example 1 / 0 static int i = 2 || 1 / 0; 被定义的行为:

  

因此,在以下初始化中,

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

表达式是一个有效的整数常量表达式,其值为1。

第6.6节,脚注118。

1/0不是常量表达式。

在Constraints下,C11标准的{p> Section 6.6

  
      
  1. 常量表达式不应包含赋值,递增,递减,函数调用或逗号运算符,除非它们包含在未评估的子表达式中。
  2.   
  3. 每个常量表达式应计算为其类型的可表示值范围内的常量。
  4.   

由于1/0不会在int表示的值范围内求值为常量,因此1/0不是常量表达式。这是关于什么算作常量表达式的规则,例如关于不在其中进行赋值的规则。您至少可以看到C ++,Clang doesn't consider 1/0 a constant expression

(x == 0) ? x : 1 / x

对于未评估的1/0而言,UB是没有意义的。

即使x为0且评估1 / x为UB,

(0 == 0) ? 0 : 1 / 0也是非常明确的。如果<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"> <!-- Copy all attributes and elements to the output. --> <xsl:template match="@*|*"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates select="*" /> </xsl:copy> </xsl:template> <xsl:output method="xml" indent="yes" /> <!-- Remove all .config files from the install of the msi This xslt is called from the heat.exe -t switch command from the .wixproj file. Unload the wixproj file and then edit to modify or remove the -t switch if .config files are to be included. --> <xsl:key name="config-search" match="wix:Component[contains(wix:File/@Source, '.config')]" use="@Id" /> <xsl:template match="wix:Component[key('config-search', @Id)]" /> <xsl:template match="wix:ComponentRef[key('config-search', @Id)]" /> </xsl:stylesheet> 是UB,那将是无稽之谈。

答案 2 :(得分:15)

来自C标准草案(N1570):

  

6.5.5乘法运算符

     

...

     
      
  1. /运算符的结果是第一个操作数除以的商   第二; %运算符的结果是余数。在两个操作中,如果值为   第二个操作数为零,行为未定义。
  2.   

关于第3章中未定义的行为。术语,定义和符号:

  

3.4.3

     
      
  1. 未定义的行为
      在使用不可移植或错误的程序结构或错误数据时,   本国际标准没有要求
  2.   
  3. 注意可能的未定义行为包括完全忽略不可预测的情况   结果,在翻译过程中表现或程序执行以有记录的方式表现出来   环境(有或没有发出诊断信息),终止翻译或   执行(发出诊断信息)。
  4.   

允许崩溃编译器。

答案 3 :(得分:2)

其他人已经提到了标准中的相关文字,所以,我不打算重复这一点。

My C编译器的表达式求值函数采用反向波兰表示法(值数组(数字和标识符)和运算符)中的表达式并返回两个内容:表达式是否为表达式求值为常量的值以及值是否为常数(否则为0)。如果结果是常数,则整个RPN减少到该常数。 1/0不是常量表达式,因为它不计算为常量整数值。 RPN不会降低1/0并保持不变。

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

我的编译器正确支持|| -expressions和&amp;&amp; -expressions中的短路。因此,它将1 || 1/0评估为1,将0 && 1/0评估为0,而不管逻辑运算符的右侧操作数是否为常量。当不能对这些运算符进行求值时,表达式求值函数会删除这些运算符的右手操作数(以及运算符),因此1 || 1/0转换为1 != 0(回想一下&amp;&amp;和& ||与0)进行比较,得到1和0 && 1/0转换为0 != 0,得到0。

需要处理的另一个案例是INT_MIN / -1INT_MIN % -1(对于较大的整数类型,同上)。商不能表示为signed int(在2的补码有符号整数的情况下,这是我们在所有现代CPU中所拥有的),所以这也是UB(在运行时x86上得到相同的除零异常) 。我同样处理这个案子。此表达式无法初始化静态存储的变量,如果未在逻辑&amp;&amp; / ||中进行评估,则该表达式将被丢弃运营商。它可以初始化一个自动变量,可能在运行时导致UB。

遇到此类分割时,我也会发出警告。

答案 4 :(得分:-1)

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

我认为,如果有这样一个表达式,这种迂腐的实现会 编译为在运行时执行1/0的代码,但我不这么认为 将被视为一个很好的功能。

所以剩下的空间是编译器应该拒绝编译它,并且 将其视为某类源代码错误。