Go Down

Topic: Unexpected unsigned-to-signed promotion (Read 1 time) previous topic - next topic

togg

I thought I understood type promotion until today.   I ran into a case where an unsigned type was silently promoted to a signed type, causing unexpected results.   I know that blaming unexpected results on compiler bugs is one of the first signs that one is losing his grip on reality, but this one has me seriously considering it.

Here's some code:
Code: [Select]

  byte b = 0x7f;
  unsigned long u = b << 8;
  Serial.print("0x7F <<  8: ");
  Serial.println(u, HEX);
 
  b = 0x80;
  u = b << 8;
  Serial.print("0x80 <<  8: ");
  Serial.println(u, HEX);


which gives me this output:
Code: [Select]

    0x7F <<  8: 7F00
    0x80 <<  8: FFFF8000



I expected b to be promoted to a 16 bit type prior to the left shift, but I didn't expect the promotion to be from an unsigned type (byte) to a signed type (int).   It seems to me that it should have been promoted to an unsigned int.

Of course, if I explicitly cast b to an unsigned type prior to the shift (e.g. u = (unsigned long)b << 8; , I get the results I expected (i.e. 0x80 << 8: 8000).

Can anyone provide a logical explanation for this unsigned-to-signed promotion, or is that just the way it is?

--
Togg

P.S. I was using the Arduino IDE version 1.0.3 on Mac OS X 10.6.8, and running the code on an Arduino Duemilanove board.

WizenedEE

constants are ints. So b << 8 is a uint8_t and a int16_t together. The bigger one is int16_t, so the compiler promotes the other to that.

When b is an unsigned long, that is bigger, so the constant is promoted to that.

I would say it's one of the more annoying features of C -- you could say it's a "standards" bug rather than a compiler bug ;)

togg


constants are ints. So b << 8 is a uint8_t and a int16_t together. The bigger one is int16_t, so the compiler promotes the other to that.


Interesting, and plausible, but note:

Code: [Select]

u = b << (byte)8;       
u = b << (unsigned int)8;

both result in u being 0xFFFF8000.

--
Togg

Nick Gammon

Quote

I know that blaming unexpected results on compiler bugs is one of the first signs that one is losing his grip on reality ...


How very true.

See this:

https://www.securecoding.cert.org/confluence/display/seccode/INT02-C.+Understand+integer+conversion+rules

Quote
Integer types smaller than int are promoted when an operation is performed on them. If all values of the original type can be represented as an int, the value of the smaller type is converted to an int; otherwise, it is converted to an unsigned int.


Both of your operands (b and 8 ) can be represented as an int so they are promoted to an int.

Change b to:

Code: [Select]

  unsigned int b = 0x7f;


And the code works as expected. This is because an unsigned int can't be promoted to an int.

Not that I knew that before I looked it up. :)

togg

Thanks to both of you for the good answers, and extra thanks to Nick for the link to the CERT page on integer conversions.  I need to spend some quality time with that one. 

Given those answers, I'm a bit surprised that this code:
Code: [Select]

#include <stdio.h>
#include <inttypes.h>

int main () {
    unsigned char b = 0x80;
    unsigned long u = b << 8;
    printf("0x80 <<  8: %lx\n", u);
    return 0;
}

compiled (with gcc) and run on my Mac desktop displays the expected 0x80 <<  8: 8000.  The actual behavior then seems to be implementation dependent.

This issue occurred in the context of assembling a 22 bit value from a device by reading in a byte at a time, like this (simplified):
Code: [Select]

unsigned long getValue() {
    unsigned long val = 0;
    val = getByte() << 16;
    val |= getByte() << 8;
    val |= getByte();
    ...
    return val;
}

byte getByte() {
...
//read byte from device
...
}


Of course there were two problems with this code due to the (at the time unexpected) conversion of byte to int: we got a very large jump in the returned value when the middle byte went from 0x7F to 0x80, and the MSByte is left shifted out of existence.

Thanks again, gentlemen.

--
Togg

Go Up