2009-11-24 43 views
8

当写这样的:条件运算符是否应该评估所有参数?

1: inline double f(double arg) { 
2: return arg == 0.0 ? 0.0 : 1./arg; 
3: } 
4: const double d = f(0.0); 

微软的Visual Studio 2005的64位编译器

line 4: warning C4723: potential divide by 0 

来虽然你和我可以清楚地看到,一个div被零是永远去发生...

或者是它?

+11

保重,比较关于平等的“双重”论点。邪恶的魔法发生在那里...... – SadSido 2009-11-24 08:16:58

+1

不,它不。完美定义的过程。特别是'0.0 == -0.0'。因此,对于定义了1./arg的所有值集合,我们知道'arg!= 0.0'。 – MSalters 2009-11-26 15:04:25

+1

@ MSalters:但由于四舍五入错误,arg可能不是0.0(或-0.0),如果您期望。 – jalf 2009-11-26 15:31:06

回答

4

这是一个明显的错误,毫无疑问。

警告的目的不是要警告程序中的所有部门。这在任何合理的计划中都会太吵。相反,当你需要检查一个参数时,意图是警告你。在这种情况下,你确实检查了参数。因此,编译器应该注意到,并闭嘴。

这种功能的技术实现是通过在具有特定属性的代码分支中标记变量来完成的。最常见的属性之一是三态“无效”。在分支之前,arg是外部变量,arg [[Isnull]]未知。但在arg检查后有两个分支。在第一个分支arg [[Isnull]]是真的。在第二个分支arg [[Isnull]]是错误的。

现在,当涉及到产生零分和空指针警告时,应该检查[[IsNull]属性。如果属实,则有严重的警告/错误。如果不知道,则应该生成上面显示的警告 - 这是一个潜在的问题,超出编译器可以证明的范围。但在这种情况下,[[isNull]]属性为False。编译器通过与人类相同的形式逻辑,知道没有风险。

但是,我们如何知道编译器在内部使用了这样的[[Isnull]]属性?回想一下第一段:没有它,它必须总是或永远警告。我们知道它有时会发出警告,但必须有[[IsNull]]属性。

+0

这是可靠的推理。我可以接受这一点 - 尤其是因为它证明了我的直觉。 – xtofl 2009-11-26 19:02:13

+0

最近的一个Linux bug是由这个属性造成的。在有问题的代码,指针被解除引用第一(基本上'INT *构件=(foo->巴);'然后检查'如果(富)回报;!'然而,因为取消引用时,'FOO [的。 [IsNull]]'属性已被设置为false,并优化NULL检查。 – MSalters 2009-11-27 10:14:21

11

编译器无法静态分析所有代码路径并始终考虑所有可能性。理论上,仅通过查看其源代码就可以完全分析程序行为,这可以提供一个解决问题的方法,这是不可判定的。编译器有一套有限的静态分析规则来检测规则。 C++标准不要求编译器发布这样的警告,所以不需要。这不是一个错误。这更像是一个不存在的功能。

+0

你说得对。从C++标准的角度来看,它永远不会是一个错误,因为它涉及警告。 但是,如果这个“漏洞”迫使我重写完全有效的代码(或'#pragma's围绕着它),从一个“计算机程序的角度,这是一个错误。 – xtofl 2009-11-24 08:07:14

+0

@xtofl:警告中的关键词是“潜在的” – 2009-11-24 08:08:08

+0

同意了,但关键是痛苦,我们试图警告视为错误,并不能与“假阳性”警告这样做。 – xtofl 2009-11-24 08:15:35

7

不,条件运算符不会评估这两个参数。但是,如果编译器可以检测到这种情况,通常会报告一个可能被零除的值。标准占用大约2页来描述这个操作符的行为并不是假的。

从N 4411:

5.16条件运算符

条件表达式组 从右到左。第一个表达式 上下文转换为bool(条款 4)。它被评估,如果它是真的, 条件 表达式的结果是第二个 表达式的值,否则 第三个表达式的值。 第二个和第三个表达式中只有一个是 评估。在与第二或第三 表达式相关的每个 值计算和副作用 之前,对与第一个 表达式相关联的每个值计算和副作用进行排序。

此外,请注意:

否则,如果第二和第三 操作数有不同的类型,并且 或者已经(可能CV修饰) 类类型,试图到 将这些操作数的每一个转换为另一个的 类型。

您引用的例子对于第二个和第三个表达式都具有相同的类型 - 放心,只有第一个将被评估。

+0

我怀疑第3点实际上说两个操作数都会被评估。我认为这是关于是否像'std :: string s(“A”); const char * c =“B”; std :: string a = cond()? s:c; const char * b = cond()? s:c;'应该编译或不编译(确定运算符的结果类型应该是什么:编译器检查什么可以转换为什么 - 在本例中是第一个::编译,因为const char *可以隐式转换到std :: string的,但第二个不编译的,因为结果类型操作的是'的std :: string',而这不能被隐式转换为'为const char *') – UncleBens 2009-11-24 16:11:25

+0

Accpeted和编辑。我应该指出这一点。 – dirkgently 2009-11-24 17:23:43

3

该部门的代码将生成,因此警告。但arg为0时决不会采用该分支,因此安全。

3

运算符==对于浮点数是不安全的(即由于舍入问题您不能信任它)。在这个特定的情况下,它实际上是安全的,所以你可以忽略这个警告,但编译器不会根据一般情况下的结果有点不可预测的运算符进行这样的分析。

3

条件运算符不应该评估所有参数。但我相信你可以把arg几乎等于0,所以arg == 0.0将是false,但是1./arg会给出“除以零”的结果。所以我认为这个警告在这里很有用。

顺便说一下,Visual C++ 2008不会给出这样的警告。

+0

这是有道理的。谢谢。 – xtofl 2009-11-24 09:31:28

+1

C中的IEEE 754浮点除法不会除以零。除以零的结果将是正无穷,负无穷大或NaN – 2009-11-24 11:52:01

+0

结果可以是正无穷大或负无穷大,对于次正规数,但我会称之为溢出而不是零师。 – starblue 2009-11-24 13:14:04

0

除了其他注释:警告是由编译器生成的,死的分支由优化器删除,稍后运行 - 甚至可能在链接阶段。

所以不,这不是一个错误。警告是由编译器提供的附加服务,不是标准的要求。这是编译器/链接器体系结构的一个不幸的副作用。

0

您可能能够避免使用特定于Microsoft的__assume关键字的警告。我不确定您是否可以将它与条件运算符绑定。否则就像

if (arg == 0.0){ 
    return 0.0; 
} 
else { 
__assume(arg != 0.0); 
    return 1./arg; 
} 

可能是值得一试。或者,当然,只需在适当的#pragma下将此警告消除。

相关问题