写这篇文章时:
1: inline double f( double arg ) {
2: return arg == 0.0 ? 0.0 : 1./arg;
3: }
4: const double d = f( 0.0 );
microsoft visual studio 2005 64位编译器附带
line 4: warning C4723: potential divide by 0
虽然你和我可以清楚地看到一个div-by-zero 永远不会发生......
或者是吗?
答案 0 :(得分:11)
编译器无法静态分析所有代码路径,并始终考虑所有可能性。理论上,仅通过查看其源代码就可以完整地分析程序行为,这可以提供解决问题的解决方案,这是不可判定的。编译器具有一组有限的静态分析规则来检测规则。 C ++标准不要求编译器发出这种警告,所以,不。这不是一个错误。这更像是一个不存在的功能。
答案 1 :(得分:7)
不,条件运算符不会计算两个参数。但是,如果编译器可以检测到这样的事情,通常会报告潜在的除零。标准占用约2页来描述该运算符的行为并非一无所获。
来自N-4411:
5.16条件运算符
1 条件表达式组 右到左。第一个表达是 语境转换为bool(条款 4)。它被评估,如果是真的, 有条件的结果 表达式是第二个的值 表达,否则表达 第三个表达。只有一个 第二和第三个表达是 评估。每个值计算和 副作用与第一个相关 表达式在每个之前排序 价值计算和副作用 与第二或第三相关联 表达
另外,请注意:
3 否则,如果是第二个和第三个 操作数有不同的类型,和 要么(可能是合格的) 类类型,尝试 将每个操作数转换为 另一种的类型。
您引用的示例对于第二个和第三个表达式具有相同的类型 - 请放心,只会对第一个表达式进行评估。
答案 2 :(得分:4)
毫无疑问,这是一个明显的错误。
警告的目的不是要警告程序中的所有分歧。在任何合理的计划中,这都太吵了。相反,目的是在需要检查参数时警告您。在这种情况下,您确实检查了参数。因此,编译器应该注意到并关闭。
通过在具有特定属性的代码分支中标记变量来完成此类功能的技术实现。最常见的属性之一是三态“Is null”。在分支之前,arg
是外部变量,arg [[Isnull]]
未知。但在arg
检查后,有两个分支。在第一个分支arg [[Isnull]]
是真的。在第二个分支中arg [[Isnull]]
为假。
现在,当涉及生成除零和空指针警告时,应检查[[IsNull]
属性。如果为true,则表示严重警告/错误。如果未知,您应该生成上面显示的警告 - 一个潜在的问题,超出了编译器可以证明的范围。但在这种情况下,[[isNull]]
属性为False。编译器采用与人类相同的形式逻辑,知道没有风险。
但是我们如何知道编译器在内部使用这样的[[Isnull]]
属性?回想一下第一段:没有它,它必须要么总是警告,要么永远警告。我们知道它有时会发出警告,因此必须有[[IsNull]]
属性。
答案 3 :(得分:3)
将生成分区的代码,因此会发出警告。 <{1}}为0时,永远不会采取分支,所以它是安全的。
答案 4 :(得分:3)
operator ==浮点数是不安全的(即由于舍入问题你不能相信它)。在这种特定情况下,它实际上是安全的,因此您可以忽略该警告,但编译器不会基于操作符进行此类分析,该操作符的结果在一般情况下有些不可预测。
答案 5 :(得分:3)
条件运算符不应评估所有参数。但我相信您可以arg
几乎等于0,因此arg == 0.0
将为false
,但1./arg
会给出“除以零”的结果。所以我认为这个警告在这里很有用。
顺便说一句,Visual C ++ 2008没有给出这样的警告。
答案 6 :(得分:0)
除了其他注释之外:警告由编译器生成,死分支由稍后运行的优化器删除 - 甚至可能在链接阶段。
所以不,这不是一个错误。警告是编译器提供的附加服务,不是标准强制要求的。这是编译器/链接器体系结构的一个不幸的副作用。
答案 7 :(得分:0)
您可以使用特定于Microsoft的__assume
关键字来避免警告。我不确定你是否可以将它与条件运算符联系起来。否则就像
if (arg == 0.0){
return 0.0;
}
else {
__assume(arg != 0.0);
return 1./arg;
}
可能值得一试。或者,当然,只需使用适当的#pragma