views:

351

answers:

8

When writing this:

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

The microsoft visual studio 2005 64-bit compiler came with

line 4: warning C4723: potential divide by 0

While you and I can clearly see that a div-by-zero is never going to happen...

Or is it?

+10  A: 

The compiler is not able to statically analyze all code paths and take into account all possibilities all the time. Theoretically, complete analysis of a program behavior just by looking into its source code can provide a solution to halting problem, which is undecidable. Compilers have a limited set of static analysis rules to detect rules. The C++ standard does not require the compiler to issue such kind of warnings, so, no. It's not a bug. It's more like a nonexistent feature.

Mehrdad Afshari
You're right. It can never be a bug from the C++ standard point of view, since it concerns warnings.However, if this 'flaw' forces me to rewrite perfectly valid code (or surround it with `#pragma`s), from a 'computer program' point of view, it's a bug.
xtofl
@xtofl: The key word in the warning is "potential"
Mehrdad Afshari
Agreed, but the key pain is that we try to treat warnings as errors, and cannot do so with 'false positive' warnings.
xtofl
@xtofl: You are using a tool. If it gives unneeded warnings, you will have to ignore them. It may be a "bug" for you, but it's the tool you are using and you cannot modify it.
Daniel Daranas
@xtofl: you could #pragma warning(disable:4723)
markh44
+6  A: 

No, the conditional operator does not evaluate both arguments. However, a potential divide-by-zero, if a compiler can detect such a thing, is typically reported. It is not for nought that the standard takes up ~2 pages to describe the behavior of this operator.

From N-4411:

5.16 Conditional operator

1 Conditional expressions group right-to-left. The first expression is contextually converted to bool (Clause 4). It is evaluated and if it is true, the result of the conditional expression is the value of the second expression, otherwise that of the third expression. Only one of the second and third expressions is evaluated. Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

Also, note:

3 Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, an attempt is made to convert each of those operands to the type of the other.

The example you have cited has the same type for both the second and the third expressions -- rest assured, only the first will be evaluated.

dirkgently
I doubt point 3 actually says that both operands will be evaluated. I think this rather concerns whether things like `std::string s("A"); const char* c = "B"; std::string a = cond() ? s : c; const char* b = cond() ? s : c;` should compile or not (determining what the result type of the operator should be: the compiler checks what can be cast to what - in this case the first ?: compiles, since `const char*` can be implicitly converted to std::string, but the second doesn't compile, since the result type of the operator is `std::string`, and this can't be implicitly converted to `const char*`)
UncleBens
Accpeted and edited. I should have specified that.
dirkgently
+3  A: 

the code for the division will be generated, hence the warning. but the branch will never be taken when arg is 0, so it is safe.

Adrien Plisson
+3  A: 

operator== for floating-point numbers is unsafe (i.e. you cannot trust it, due to rounding issues). In this specific case it is actually safe, so you can ignore the warning, but the compiler will not make such an analysis based on an operator whose results are somewhat unpredictable in the general case.

Gorpik
+3  A: 

The conditional operator shouldn't evaluate all arguments. But I believe you could take arg almost equal to 0, so arg == 0.0 will be false, but 1./arg will give "division by zero" result. So I think that warning is useful here.

By the way, Visual C++ 2008 doesn't give such warning.

Kirill V. Lyadvinsky
That makes sense. Thanks.
xtofl
IEEE 754 floating point divisions in C will not throw divide by zero. The result of dividing by zero will be +Infinity, -Infinity, or NaN
Mehrdad Afshari
The result can be +Infinity or -Infinity for subnormal numbers, but I would call that an overflow rather than a division by zero.
starblue
@starblue: almost correct. `1./arg` cannot overflow. `10.0/arg` could overflow, though. (again assuming IEEE754, 1E0 / 2E-308 = 5E307 which is < 1.7E308)
MSalters
A: 

In addition to the other comments: the warning is generate by the compiler, the dead branch is removed by the optimizer which runs later - possibly even at link stage.

So no, it's not a bug. The warning is an additional service provided by the compiler, not mandated by the standard. It's an unfortunate side effect of the compiler / linker architecture.

peterchen
+2  A: 

It's an obvious bug, beyond doubt.

The intent of the warning is NOT to warn about all divisions in a program. That would be far too noisy in any reasonable program. Instead, the intent is to warn you when you need to check an argument. In this case, you did check the argument. Hence, the compiler should have noted that, and shut up.

The technical implementation of such a feature is done by labelling variables in code branches with certain attributes. One of the most common attributes is the tri-state "Is null". Before the branch, arg is an external variable and arg [[Isnull]] is unknown. But after the check on arg there are two branches. In the first branch arg [[Isnull]] is true. In the second branch arg [[Isnull]] is false.

Now, when it comes to generating divide-by-zero and null pointer warnings, the [[IsNull] attribute should be checked. If true, you have a severe warning/error. If unknown, you should generate the warning shown above - a potential problem, beyond what the compiler can prove. But in this case, the [[isNull]] attribute is False. The compiler by the same formal logic as humans, knows that there is no risk.

But how do we know that the compiler is using such an [[Isnull]] attribute internally? Recall the first paragraph : without it, it would have to either warn always or never. We know it warns sometimes, ergo there must be an [[IsNull]] attribute.

MSalters
That's solid reasoning. I can live with that -especially since it proves my gut feeling.
xtofl
A recent Linux bug was caused by exactly this attribute. In the offending code, a pointer was first dereferenced (basically `int* member = ` and then checked `if (!foo) return;`. However, because of the dereference, the `foo[[IsNull]]` attribute was already set to false, and the NULL check optimized out.
MSalters
A: 

You might be able to avoid the warning using the Microsoft-specific __assume keyword. I'm not sure if you can tie it in with the conditional operator. Otherwise something like

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

may be worth a shot. Or, of course, just silence the warning during this function, with the appropriate #pragma.

jalf