C + + 警告: 双除以零

案例1:

#include <iostream>


int main()
{
double d = 15.50;
std::cout<<(d/0.0)<<std::endl;
}

它可以在没有任何警告的情况下编译并打印 inf

但是,

案例2:

#include <iostream>


int main()
{
double d = 15.50;
std::cout<<(d/0)<<std::endl;
}

编译器给出以下警告(现场直播) :

warning: division by zero [-Wdiv-by-zero]
std::cout<<(d/0)<<std::endl;

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

0 != 0.0吗?

编辑:

#include <iostream>


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

产出:

Same
12852 次浏览

In Standard C++, both cases are undefined behaviour. Anything may happen, including formatting your hard drive. You should not expect or rely on "return inf. Ok" or any other behaviour.

The compiler apparently decides to give a warning in one case and not the other, but this doesn't mean that one code is OK and one isn't. It is just a quirk of the compiler's generation of warnings.

From the C++17 standard [expr.mul]/4:

The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of ABC0 or % is zero the behavior is undefined.

Floating point division by zero is well defined by IEEE and gives infinity (either positive or negative according to the value of the numerator (or NaN for ±0) ).

For integers, there is no way to represent infinity and the language defines the operation to have undefined behaviour so the compiler helpfully tries to steer you clear from that path.

However in this case, since the numerator is a double, the divisor (0) should be promoted to a double too and there's no reason to give a warning here whilst not giving a warning for 0.0 so I think this is a compiler bug.

I will not go into the UB / not UB debacle in this answer.

I just want to point that 0 and 0.0 are different despite 0 == 0.0 evaluating to true. 0 is an int literal and 0.0 is a double literal.

However in this case the end result is the same: d/0 is floating point division because d is double and so 0 is implicitly converted to double.

My best guess to answer this particular question would be that compiler emits warning before performing conversion of int to double.

So, the steps would be like this:

  1. Parse expression
  2. Arithmetic operator /(T, T2), where T=double, T2=int.
  3. Check that std::is_integral<T2>::value is true and b == 0 - this triggers warning.
  4. Emit warning
  5. Perform implicit conversion of T2 to double
  6. Perform well defined division (since compiler decided to use IEEE 754).

This is of course speculation and is based on compiler-defined specifications. From standard point of view, we're dealing with possible Undefined Behaviours.


Please note that this is expected behaviour according to GCC documentation
(btw. it seems that this flag can't be used explicitly in GCC 8.1)

-Wdiv-by-zero
Warn about compile-time integer division by zero. This is default. To inhibit the warning messages, use -Wno-div-by-zero. Floating point division by zero is not warned about, as it can be a legitimate way of obtaining infinities and NaNs.

Floating point division by zero behaves differently than integer division by zero.

The IEEE floating point standard differentiates between +inf and -inf, while integers cannot store infinity. Integer division by zero result is undefined behaviour. Floating point division by zero is defined by the floating point standard and results in +inf or -inf.

I would argue that foo/0 and foo/0.0 are not the same. Namely, the resulting effect of the first (integer division or floating point division) is highly dependant on the type of foo, while the same is not true for the second (it will always be a floating point division).

Whether any of the two is UB is irrelevant. Quoting the standard:

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

(Emphasis mine)

Consider the "suggest parentheses around assignment used as truth value" warning: The way to tell the compiler that you really want to use the result of an assignment is by being explicit, and adding parenthesis around the assignment. The resulting statement has the same effect, but it tells the compiler you know what you're doing. The same can be said about foo/0.0: Since you're explicitly telling the compiler "This is floating point division" by using 0.0 instead of 0, the compiler trusts you and will not issue a warning.

This looks like a gcc bug, the documentation for -Wno-div-by-zero clearly says:

Do not warn about compile-time integer division by zero. Floating-point division by zero is not warned about, as it can be a legitimate way of obtaining infinities and NaNs.

and after Usual arithmetic conversions covered in [expr.arith.conv] both operands will be double:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

...

Otherwise, if either operand is double, the other shall be converted to double.

and [expr.mul]:

The operands of * and / shall have arithmetic or unscoped enumeration type; the operands of % shall have integral or unscoped enumeration type. The usual arithmetic conversions are performed on the operands and determine the type of the result.

With respect to whether floating point divide by zero is undefined behavior and how different implementation deal with it seem my answer here. TL;DR; It looks like gcc conforms to Annex F wrt to floating point divide by zero, so undefined does not play a role here. The answer would be different for clang.