C++警告:将double除以零

Jay*_*esh 97 c++ floating-point gcc divide-by-zero

情况1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)

它编译时没有任何警告和打印inf.好的,C++可以处理除零,(实时查看).

但,

案例2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)

编译器发出以下警告(请参见实时):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;
Run Code Online (Sandbox Code Playgroud)

为什么编译器会在第二种情况下发出警告?

0 != 0.0吗?

编辑:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Same
Run Code Online (Sandbox Code Playgroud)

Mot*_*tti 107

浮点除以零由IEEE很好地定义并给出无穷大(根据分子的值(或NaN对于±0)为正或负).

对于整数,没有办法表示无穷大,语言定义了具有未定义行为的操作,因此编译器有助于引导您从该路径中清除.

但是在这种情况下,由于分子是a double,divisor(0)也应该被提升为double,并且没有理由在这里发出警告而没有给出警告,0.0所以我认为这是一个编译器错误.

  • 请注意,C++不需要使用IEEE 754(即使我从未见过使用不同标准的编译器). (43认同)
  • 我同意它应该在两种情况下发出警告,或者在两种情况下都不发出警告(取决于编译器对浮动除法的处理是什么) (14认同)
  • 两者都是浮点除法.在`d/0`中,`0`被转换为`d`的类型. (8认同)
  • 很确定浮点除以零也是UB - 只是GCC根据IEEE 754实现它.它们不必这样做. (8认同)

M.M*_*M.M 41

在标准C++中,两种情况都是未定义的行为.可能发生任何事情,包括格式化硬盘.你不应该期望或依赖"返回inf.好"或任何其他行为.

编译器显然决定在一个案例而不是另一个案例中发出警告,但这并不意味着一个代码是好的而一个代码没有.这只是编译器生成警告的一个怪癖.

从C++ 17标准[expr.mul]/4:

二元/运算符产生商,二元%运算符从第一个表达式除以第二个表达式得到余数.如果第二个操作数为/%为零,则行为未定义.

  • 不是这样,在浮点运算中,除以零是明确定义的. (21认同)
  • @Motti - 如果仅限于C++标准,那么就没有这样的保证.坦率地说,这个问题的范围没有明确规定. (9认同)
  • @StoryTeller我很确定(虽然我没有看过标准文档本身)如果`std :: numeric_limits <T> :: is_iec559`是'true`,那么`T`除以零是不是UB(在大多数平台上,对于`double`和`float`来说它是'true`,虽然要便携,你需要用`if`或`if constexpr`明确检查它. (9认同)
  • @DanielH - "合理"实际上是非常主观的.如果这个问题被标记为[tag:language-lawyer],那么将会有一整套其他(小得多)合理的假设. (6认同)
  • @MM请记住,这正是你所说的,但仅此而已:未定义并不意味着没有强制要求,这意味着标准*没有要求*.我认为在这种情况下,实现将`is_iec559`定义为`true`这一事实意味着*implementation*是记录标准未定义的行为.只是这是一个可以通过编程方式读取实现文档的情况.甚至不是唯一的:对于有符号整数类型,同样适用于`is_modulo`. (5认同)
  • @MM根据你的逻辑,是否*任何*实现允许将`is_iec559`定义为`true`?如果是这样,在什么情况下呢? (2认同)
  • @hvd但你的整个论点是,如果实现将`is_iec559`定义为`true`,那么C++标准强制要求该类型遵循IEC559.'is_modulo`主题是一个完整的争论; 当前标准明确地说"`is_modulo`对于有符号整数类型(6.9.1)是假的,除非作为本国际标准的扩展的实现定义了有符号整数溢出来包装".对于`is_iec559`没有任何类似的陈述(但也许应该有,如果这是意图) (2认同)

Yks*_*nen 12

我最好的猜测来回答此问题的讨论将是编译器发出警告之前的进行转换intdouble.

所以,步骤将是这样的:

  1. 解析表达式
  2. 算术运算符 /(T, T2),其中T=double,T2=int.
  3. 检查std::is_integral<T2>::valuetrueb == 0-这将触发警告.
  4. 发出警告
  5. 执行隐式转换T2double
  6. 执行定义明确的划分(因为编译器决定使用IEEE 754).

这当然是推测,并且基于编译器定义的规范.从标准的角度来看,我们正在处理可能的未定义行为.


请注意,根据GCC文档,这是预期的行为
(顺便说一下,似乎这个标志不能在GCC 8.1中明确使用)

-Wdiv-by-zero
警告编译时整数除零.这是默认值.要禁止显示警告消息,请使用-Wno-div-by-zero.没有警告浮点除零,因为它可以是获得无穷大和NaN的合法方式.

  • 这不是C ++编译器的工作方式。编译器必须在`/`上执行重载解析才能知道它是除法的。如果左侧是一个“ Foo”对象,并且会有一个“ operator /(Foo,int)”,那么它甚至可能不是除法的。编译器仅在使用右侧的隐式转换选择“内置/(双精度,双精度)”时才知道其除法。但这意味着它没有被int(0)除,而是被double(0)除。 (2认同)
  • 我们在这里谈论的是内置运算符。那真是有趣。它实际上不是功能,因此您不能使用其地址。因此,您无法确定`operator /(double,int)`是否确实存在。例如,编译器可以决定通过将常量“ b”替换为“ a *(1 / b)”来优化常量a的a / b。当然,这意味着您不再在运行时调用`operator /(double,double)`,而是更快的`operator *(double,double)`。但是现在是优化器跳出“ 1/0”,这是它必须提供给“ operator *”的常数。 (2认同)
  • @ user202729:即使对于“整数”除法,GCC也会这样做。让它陷入片刻。GCC用整数乘法代替整数除法。是的,这是可能的,因为GCC知道它在环上运行(数字以2 ^ N为模) (2认同)

bol*_*lov 9

在这个答案中,我不会进入UB /非UB的崩溃.

我只是想指出这一点0,并0.0 有不同的,尽管0 == 0.0评估为true.0int文字,0.0double文字.

但是在这种情况下,最终结果是相同的:d/0是浮点除法因为d是double,所以0隐式转换为double.

  • 我没有看到它是如何相关的,因为通常的算术转换指定将`double`除以`int`意味着`int`被转换为`double`,并且在标准中指定了` 0`转换为`0.0`(conv.fpint/2) (5认同)
  • 问题是"是'0!= 0.0`?".OP永远不会问他们是否"相同".在我看来,问题的目的是否与"d/0"的行为是否与"d/0.0"不同. (2认同)
  • @MM - [OP确实问过](https://stackoverflow.com/revisions/51474239/5).他们并没有真正展示那些常规编辑的体面礼仪. (2认同)

Cás*_*nan 7

我认为,foo/0foo/0.0一样的.也就是说,第一个(整数除法或浮点除法)的结果效果高度依赖于类型foo,而第二个(它将始终是浮点除法)则不是这样.

两者中的任何一个是UB是无关紧要的.引用标准:

允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定行为,终止翻译或执行(发布时)一条诊断信息).

(强调我的)

考虑" 建议用作真值的赋值括号 "警告:告诉编译器你真的想要使用赋值结果的方法是明确,并在赋值周围添加括号.结果语句具有相同的效果,但它告诉编译器您知道自己在做什么.可以这样说foo/0.0:由于您通过使用0.0而不是明确告诉编译器"这是浮点除法" 0,编译器会信任您并且不会发出警告.