0

While writing a program for uni, I noticed that

unsigned char byte_to_write_1 = (0xFF << 2) >> 2; ==> 0xFF (wrong)
unsigned char byte_to_write_2 = (0xFF << 2);
byte_to_write_2 = byte_to_write_2 >> 2; ==> 0x3F (correct)

I don't understand what's causing the discrepancy... my best guess is that while modifying a byte with multiple operations on the same line, C "holds onto" the extra bits in a slightly larger datatype until the line is terminated so 0xFF << 2 is held as 11[1111 1100] instead of 1111 1100 so on the same-line shiftback, the result is 1111 1111 instead of 1111 1100.

What causes the difference in results? Thanks in advance...

I first noticed the issue in a larger code project but have been able to recreate the issue using a much more simple program.image of simplified code to showcase problem

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 4
    Because your RHS operations on constants are done as `int` type (signed!). The second one is done after the result of first step was truncated to fit in `unsigned char` – Eugene Sh. Feb 07 '23 at 19:04
  • It's interesting what OP deems to be 'correct'. At face value those labels are reversed, and with the use of constants is rather contrived. If the aim was to clear the top bit, then `byte_to_write &= 0x7F` will do it. – Weather Vane Feb 07 '23 at 19:10

2 Answers2

2
unsigned char byte_to_write_1 = (0xFF << 2) >> 2;  // 0xFF

0xFF is an int. (Obtaining 0xFF from an unsigned char wouldn't change anything since it would get promoted to an int.) 0xFF << 2 is 0x3FC. 0x3FC >> 2 is 0xFF.


unsigned char byte_to_write_2 = 0xFF << 2;  // 0xFC
byte_to_write_2 >>= 2;                      // 0x3F

We've already established that 0xFF << 2 is 0x3FC. But you assign that to an unsigned char which is presumably only 8 bits in size. So you end up assigning 0xFC instead. (gcc warns about this if you enable warnings as you should.)

And of course, you get the desired value when you right-shift that.


Solutions:

(unsigned char)( 0xFF << 2 ) >> 2
( ( 0xFF << 2 ) & 0xFF ) >> 2
0xFF & 0x3F

Demo on Compiler Explorer.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    `0xFF` is definitely an `int`, but even if it were an `unsigned char`, it would still be subject to *promotion* to `int` in the context of the shift expression. So, for example, although the desired behavior can be obtained by casting the *result* of the first shift, it cannot be obtained by casting either or both of the *operands* of that shift. The types of the operands is a secondary issue. – John Bollinger Feb 07 '23 at 19:37
  • @John Bollinger, yeah, I discovered that while writing my answer. Still, I felt that adding "what if we cast?" bit would be too much for the answer. Good as a comment, though :) – ikegami Feb 07 '23 at 19:39
  • 1
    @John Bollinger, Found a non-distracting way to add a mention of integer promition. – ikegami Feb 07 '23 at 19:41
1

The difference between these two code snippets

unsigned char byte_to_write_1 = (0xFF << 2) >> 2; ==> 0xFF (wrong)

and

unsigned char byte_to_write_2 = (0xFF << 2);
byte_to_write_2 = byte_to_write_2 >> 2; ==> 0x3F (correct)

is that in the second code snippet there is used the variable byte_to_write_2 to store the intermediate result of the expression (0xFF << 2). The variable can not hold the full integer result. So the integer result is converted to the type unsigned char that can store only one byte.

The maximum value that can be stored in an object of the type unsigned character is 0xFF

— maximum value for an object of type unsigned char
UCHAR_MAX 255 // 28 − 1

that is equal to 255. While the expression 0xFF << 2 that is equivalent to 255 * 4 can not fit in an object of the type unsigned char.

In the first code snippet the intermediate result of the expression (0xFF << 2) has the type int due to the integer promotion and can be used without a change in the full expression (0xFF << 2) >> 2.

Consider the outputs of these two calls of printf

printf( "0xFF << 2 = %d\n", 0xFF << 2 );
printf( "( unsigned char )( 0xFF << 2 ) = %d\n", ( unsigned char )( 0xFF << 2 ) );

They are

0xFF << 2 = 1020
( unsigned char )( 0xFF << 2 ) = 252
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335