2

I ran across unexpected behaviour while programming in C for an 8-bit AVR microcontroller: Consider the following:

unsigned char a = 0xFF, b = 0xFF;
unsigned short c = ((a>>4)<<8)+b;
printf("%x",c);

Where the high order nibble of byte a contains bits 8..11 and the byte b contains bits 7..0 of a 12-bit value c. The intent of the code was originally to remove the unwanted lower nibble of byte a, and then combine a and b to yield the value of c. However later on I realized the code should not work, as a is an 8-bit value, and shifting it 8 bits to the left should result in clearing the byte to 0, and a final result of 0x00FF. Instead, the code produces a result of 0x0FFF, as originally intended. The code produces the same result both on the microcontroller (avr-gcc) and on the PC (gcc). What is the cause of this behaviour?

jms
  • 719
  • 2
  • 8
  • 18
  • 1
    The operands of `>>` are expanded per standard C rules (which I forget at the moment). But basically `a` is expanded to `int`. – Hot Licks Jan 30 '14 at 21:59

2 Answers2

4

The integral promotion is performed for integral types that have ranks less than the rank of int when shift operators are used. So in this expression

(a>>4 )

a is converted to type int.

From the C++ Standard

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • I guess it promotes to an unsigned int? – Joky Jan 30 '14 at 22:05
  • 1
    No, values of type char can be placed in type int without looses. – Vlad from Moscow Jan 30 '14 at 22:06
  • Just don't bitshift a signed type. – this Jan 30 '14 at 22:08
  • 1
    In this case there is no problem because a is unsigned char and the promoted value is unsigned. – Vlad from Moscow Jan 30 '14 at 22:10
  • @VladfromMoscow bitshift of signed integer is implementation-defined behaviour. But thanks, it is interesting to keep in mind that we lose the "unsigned" in the process. – Joky Jan 30 '14 at 22:10
  • There is no implementation-defined behaviour because value of a is positive value as a is unsigned character. So the signed bit does not take part in the process. – Vlad from Moscow Jan 30 '14 at 22:12
  • @Joky In this case there is no signed types, but OP mentioned char. – this Jan 30 '14 at 22:12
  • @self unsigned char - it is important. – Vlad from Moscow Jan 30 '14 at 22:12
  • @self the promotion ends with a signed type. – Joky Jan 30 '14 at 22:13
  • @Joky But the value is positive and the signed bit is equal to zero. So there is no any problem. – Vlad from Moscow Jan 30 '14 at 22:14
  • @Joky Can you show where in the OP's code you get a signed value? I don't see it. – this Jan 30 '14 at 22:16
  • @chux no, Vlad is right, promotion is to an int as long as it can represent ALL values from the promoted type. – Joky Jan 30 '14 at 22:20
  • 1
    @chux a in converted to signed int. But the promoted value is positive and the sign bit is equal to zero. So there is no any problem. – Vlad from Moscow Jan 30 '14 at 22:20
  • @self : the unsigned char is promoted to signed int before the shift. As Vlad says, it is safe because we know that at runtime there can't be any negative value here. – Joky Jan 30 '14 at 22:21
  • I have a SO ref that cite the C99 and the C11 standard: http://stackoverflow.com/questions/11203015/integer-promotion-with-the-operator ; here is the quote from C11 "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.." – Joky Jan 30 '14 at 22:24
  • @Joky I see the error of my ways (C11 §6.3.1.1 2) "I will diminish, and go into the West". – chux - Reinstate Monica Jan 30 '14 at 22:28
  • 1
    @Joky I was convinced a narrower unsigned would get promoted to unsigned int, and int to int. But instead we get the unsigned char to int, which seems weird. – this Jan 30 '14 at 22:35
  • @self I agree about narrower `unsigned` to `unsigned`, but C's chapter and verse says otherwise. – chux - Reinstate Monica Jan 31 '14 at 00:09
1

In C, most math on smaller types results in an int. You can get the answer you want by casting the result back to unsigned char or uint8_t.

nmichaels
  • 49,466
  • 12
  • 107
  • 135