Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an operator precedence problem I'm missing? Compare of unsigned short with inverse fails

Tags:

c++

c

I can THINK of why this wouldn't work, but I don't understand why many of the workarounds I've tried don't work. Below is an example of the code I'm trying to make work. The intent should be obvious, but compiling with GCC 7.4.0 for Windows 32 bit, Visual C 32 bit and Visual C 64 bit as well as the same compilers in C++ modes, all of them result in the same answer, so I'm sure it's not just a compiler bug.

The code is:

unsigned short usAlgo = 0x0001;
unsigned short usNotAlgo = ~usAlgo;

if ( usAlgo == ~usNotAlgo )
    printf("Pass\n");
else
    printf("Fail\n");

On all the compilers I've tried, this code prints "Fail". By a slight rearrangement to:

unsigned short usCheck = ~usNotAlgo;
if ( usAlgo == usCheck )

It prints "Pass". I would have thought the usCheck would get optimized out anyway, so why is this different?

I have tried all kinds of workarounds that don't work, with bit masking, parentheses, making them signed values, and such like:

if ( usAlgo == (~usNotAlgo) & 0xffff )

or

if ( (unsigned int)(usAlgo) == ~(unsigned int)(usNotAlgo) )

I think I've discovered that the first of those two fails because '==' has a higher order of precedence than '&', but I can't for the life of me understand why the simple:

if ( usAlgo == ~usNotAlgo )

fails.

Looking at the compiler output doesn't REALLY help, other than I can see the "real" comparison ends up being:

if( 0x00000001 == 0xFFFF0001 )

implying, the unsigned short (0xFFFE) was first promoted to an unsigned int (0x0000FFFE) and THEN negated. (That's why we thought making them signed might sign extend to 0xFFFFFFFE.

I obviously have the answer to how to fix this, but I need to understand WHY.

Any ideas?

[Edit: Grammar]

like image 975
FreezerBurnt Avatar asked Jul 01 '20 23:07

FreezerBurnt


2 Answers

As you have noticed, usNotAlgo was promoted to type int before the ~ operator was applied. Generally speaking, anytime a type smaller than int is used in an expression, it is first promoted to int.

This is documented in section 6.3.1.1p2 of the C standard:

The following may be used in an expression wherever an int or unsigned int may be used:

  • An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.
  • A bit-field of type _Bool, int, signed int,or unsigned int.

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.

Section 6.5.3.3p4 regarding the ~ operator specifically says:

The result of the ~ operator is the bitwise complement of its (promoted) operand (that is,each bit in the result is set if and only if the corresponding bit in the converted operand is not set). The integer promotions are performed on the operand, and the result has the promoted type. If the promoted type is an unsigned type, the expression ~E is equivalent to the maximum value representable in that type minus E.

This can be fixed by casting the result back to unsigned short to mask off the additional bits:

if ( usAlgo == (unsigned short)~usNotAlgo )
like image 134
dbush Avatar answered Oct 17 '22 08:10

dbush


The problem is that by writing ~usNotAlgo in the if statement, it got promoted to an int value, and then because of comparison, usAlgo value got promoted to int as well. This is why you see if(0x00000001 == 0xFFFF0001) output from the compiler (instead of expected if( 0x0001 == 0x0001 )).

In order to fix it, cast ~usNotAlgo to unsigned short:

if (usAlgo == (unsigned short)~usNotAlgo) {code...}
like image 2
guyr79 Avatar answered Oct 17 '22 09:10

guyr79