13

I have the following simple C++ code:

#include "stdafx.h"
int main()
{
    int a = -10;
    unsigned int b = 10;
    //  Trivial error is placed here on purpose to trigger a warning.
    if( a < b ) {
        printf( "Error in line above: this will not be printed\n" );
    }
    return 0;
}

Compiled using Visual Studio 2010 (default C++ console application) it gives warning C4018: '<' : signed/unsigned mismatch" on line 7 as expected (code has a logical error).

But if I change unsigned int b = 10; into const unsigned int b = 10; warning disappears! Are where any known reasons for such behavior? gcc shows warning regardless of const.

Update

I can see from comments that lot of people suggest "it's just got optimized somehow so where is no warning needed". Unfortunately, warning is needed, since my code sample has actual logical error carefully placed to trigger a warning: the print statement will not be called regardless that -10 is actually less than 10. This error is well known and "signed/unsigned warning" is raised exactly to find such errors.

Update

I can also see from comments that lot of people have "found" a signed/unsigned logical error in my code and are explaining it. Where is no need to do so - this error is placed purely to trigger a warning, is trivial (-10 is conveted to (unsigned int)-10 that is 0xFFFFFFFF-10) and question is not about it :).

grigoryvp
  • 40,413
  • 64
  • 174
  • 277
  • 5
    Sounds like a compiler bug. It might be optimizing away the variable, so you just have `a < 10`. – Apples Mar 25 '13 at 20:18
  • 3
    @DBRalir: no, wait, that's it! a `const` variable of integer type initialized with a compile-time constant expression... it _is_ `a<10`. – Mooing Duck Mar 25 '13 at 20:19
  • on gcc 4.4 it don't show any waring with const or without const – Grijesh Chauhan Mar 25 '13 at 20:20
  • Yep -- with the variable being `const`, you are saying "this value cannot change from what it was initialized with". So it can make sure that the signed/unsigned mismatch doesn't cause a problem, and as such no warning needed. (if you cast away that `const`ness to change `b`, what you did was undefined behavior, so that is your problem) – Yakk - Adam Nevraumont Mar 25 '13 at 20:28
  • 1
    @DBRalir since "debug" is `not` printed, condition is evaluated incorrectly, so warning must be given in any case. – grigoryvp Mar 25 '13 at 20:51
  • @DBRalir: Is that necessarily a bug? `(signed) a < 10` is safe, and if it does optimize out the variable entirely, there's no longer signed/unsigned comparison (is there?). It knows that the value is safe and cannot (short of dishonesty by other code ignoring the const qualifier) change. – ssube Mar 25 '13 at 20:51
  • 1
    @Yakk Since debug is `not` printed, the "it can make sure that the signed/unsigned mismatch doesn't cause a problem, and as such no warning needed" statement can't be correct - where is an obvious error in code above, adding 'const' do not remove error but warning about it is removed. – grigoryvp Mar 25 '13 at 20:52
  • What does your debugger show? – StarPilot Mar 25 '13 at 20:55
  • @EyeofHell Your printf is missing newline... Add it or use puts, and see if that makes it print right. – hyde Mar 25 '13 at 20:56
  • 1
    @StarPilot "-10" is compared to "10" and "print" is not executed. – grigoryvp Mar 25 '13 at 20:58
  • @hyde Unfortunately, "\n" is not connected to waring problem at all. But i placed it (really), just in case :). Situation has not changed. – grigoryvp Mar 25 '13 at 20:59
  • @GrijeshChauhan: gcc does warn if you give it the right options; try `-Wall -Wextra` – Keith Thompson Mar 25 '13 at 21:00
  • 2
    I'd hesitate to call that an error; the comparison is evaluated exactly as the language standard requires. It's likely to be a *logical* error (i.e., not what the programmer intended). – Keith Thompson Mar 25 '13 at 21:02
  • @Keith Thompson Thanks, changed "error" to "logical error" to clear this out. – grigoryvp Mar 25 '13 at 21:04
  • 1
    My debugger shows that `FFFFFFF6` (-10) is not less than `0Ah` (10) and hence the program jumps over the print code block. – StarPilot Mar 25 '13 at 21:09
  • @KeithThompson i having gcc4.4 .5it doesn't produce warning in any ways i tried. but i also came to know few days back that [it is buggy](http://stackoverflow.com/questions/14860189/j-i-i-and-j-i-i-should-an-error-lvalue) compiler. – Grijesh Chauhan Mar 25 '13 at 21:09
  • 1
    There is mention of this bug at [Make Visual Studio warn for unsigned comparisons](http://stackoverflow.com/questions/10148591/make-visual-studio-warn-for-unsigned-comparisons): `The issue appears to be only indirectly related to usage of sizeof. The compiler also fails to emit the warning in other cases where a constant value of unsigned type is involved in the comparison.` Unfortunately, the link to the bug is dead. – Jesse Good Mar 25 '13 at 21:10
  • @KeithThompson few days back we answered a question together `char `==` unsigned char` giving false and people down-voted me heavily. but we should take care of datatype when we compare two variables at-least in C. its quite a compiler dependent. – Grijesh Chauhan Mar 25 '13 at 21:15
  • @GrijeshChauhan: Some aspects (like whether plain `char` is signed or unsigned) are compiler-specific. Others, like the behavior of `-10 < 10U`, are defined by the language -- though perhaps not in entirely intuitive ways. Yes, it can be confusing. – Keith Thompson Mar 25 '13 at 21:20

1 Answers1

9

It is a Visual Studio bug, but let's start by the aspects that are not bugs.

Section 5, Note 9 of the then applicable C++ standard first discusses what to do if the operands are of different bit widths, before proceeding what to do if they are the same but differ in the sign:

... Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

This is where we learn that the comparison has to operate in unsigned arithmetic. We now need to learn what this means for the value -10.

Section 4.6 tells us:

If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2 n where n is the number of bits used to represent the unsigned type). [Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). — end note ] 3 If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

As you can see, a specific pretty high value (4294967286, or 0xFFFFFFF6, assuming unsigned int is a 32-bit number) is being compared with 10, and so the standard guarantees that printf is really never called.

Now you can believe me that there is no rule in the standard requiring a diagnostic in this case, so the compiler is free not to issue any. (Indeed, some people write -1 with the intention of producing an all-ones bit pattern. Others use int for iterating arrays, which results in signed/unsigned comparisons between size_t and int. Ugly, but guaranteed to compile.)

Now Visual Studio issues some warnings "voluntarily".

This results in a warning already under default settings (level 3):

int a = -10;
unsigned int b = 10;
if( a < b ) // C4018
{
    printf( "Error in line above: this will not be printed\n" );
}

The following requires /W4 to get a warning. Notice that the warning was reclassified. It changed from a warning C4018 to a warning C4245. This is apparently by design. A logic error that breaks a comparison nearly always is less dangerous than one that appears to work with positive-positive comparisons but breaks down with positive-negative ones.

const int a = -10;
unsigned int b = 10;
if( a < b ) // C4245
{
    printf( "Error in line above: this will not be printed\n" );
}

But your case was yet different:

int a = -10;
const unsigned int b = 10;
if( a < b ) // no warning
{
    printf( "Error in line above: this will not be printed\n" );
}

And there is no warning whatsoever. (Well, you should retry with -Wall if you want to be sure.) This is a bug. Microsoft says about it:

Thank you for submitting this feedback. This is a scenario where we should emit a C4018 warning. Unfortunately, this particular issue is not a high enough priority to fix in the next release given the resources that we have available.

Out of curiosity, I checked using Visual Studio 2012 SP1 and the defect is still there - no warning with -Wall.

Jirka Hanika
  • 13,301
  • 3
  • 46
  • 75
  • You're probably right about that `-1` thing, I think I've done that myself. It would make more sense to use `~0` though. – Mark Ransom Mar 25 '13 at 22:07
  • @MarkRansom: the theoretical trouble with `~0` is that it has type `int`, and its behavior and value depends on the signed representation. Obviously in practice everything uses 2's complement and so `~0 == -1`. If you want to portably maximize an unsigned type (without repeating the name of the type or using `decltype`), then you need to assign `-1` or `~0U`. Use `-1` to emphasize that unsigned types are arithmetic modulo `2^n`, and `~0U` to emphasize that unsigned types are bitfields. – Steve Jessop Mar 25 '13 at 22:20
  • Oops, I should have said use `~0ULL` in my comment above, since of course `~0U` fails if the destination type is bigger than `unsigned int`. If you're willing to rely on a particular type then there's `UINT_MAX`, no need for `-` or `~`. Personally I use `-1` except when bit-twiddling. – Steve Jessop Mar 26 '13 at 01:22
  • I have changed warning level from 3 (default) to 4 (near maximum) - i can't see any C4245 warnings :(. Have you checked it for yourself? – grigoryvp Mar 26 '13 at 05:27
  • 1
    @EyeofHell - Oops - it depends on where you put the `const`. Answer updated to cover both ways. – Jirka Hanika Mar 26 '13 at 09:16