Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is shift operator evaluated in C?

Tags:

I recently noticed a (weird) behavior when I conducted operations using shift >> <<!

To explain it, let me write this small runnable code that does two operations which are supposed to be identical(In my understanding), but I'm surprised with different results!

#include <stdio.h>  int main(void) {     unsigned char a=0x05, b=0x05;      // first operation     a = ((a<<7)>>7);      // second operation     b <<= 7;     b >>= 7;      printf("a=%X b=%X\n", a, b);     return 0; }  

When ran, a = 5 and b = 1. I expect them both to be equal to 1! Can someone kindly explain why I got such a result?

P.S: In my environment the size of unsigned char is 1 byte

like image 302
chouaib Avatar asked Sep 23 '14 07:09

chouaib


People also ask

How do shift operators work in C?

Left Shift and Right Shift Operators in C/C++ Takes two numbers, left shifts the bits of the first operand, the second operand decides the number of places to shift.

How does a shift operator work?

A shift operator performs bit manipulation on data by shifting the bits of its first operand right or left. The next table summarizes the shift operators available in the Java programming language. Each operator shifts the bits of the first operand over by the number of positions indicated by the second operand.

What is the value of shift operator?

In mathematics, and in particular functional analysis, the shift operator also known as translation operator is an operator that takes a function x ↦ f(x) to its translation x ↦ f(x + a). In time series analysis, the shift operator is called the lag operator.

How does left shift operator work in C?

Use of the Left Shift Operator in C It does so by adding zeros to the right side of the value in the empty spaces that get created due to shifting. It shifts the bits available for the first operand to the left on the basis of the number of positions that the second operand specifies.


2 Answers

In the first example:

  • a is converted to an int, shifted left, then right and then converted back to usigned char.

This will result to a=5 obviously.

In the second example:

  • b is converted to int, shifted left, then converted back to unsigned char.
  • b is converted to int, shifted right, then converted back to unsigned char.

The difference is that you lose information in the second example during the conversion to unsigned char

like image 66
thumbmunkeys Avatar answered Sep 30 '22 05:09

thumbmunkeys


Detailed explanation of the things going on between the lines:

Case a:

  • In the expression a = ((a<<7)>>7);, a<<7 is evaluated first.
  • The C standard states that each operand of the shift operators is implicitly integer promoted, meaning that if they are of types bool, char, short etc (collectively the "small integer types"), they get promoted to an int.
  • This is standard practice for almost every operator in C. What makes the shift operators different from other operators is that they don't use the other kind of common, implicit promotion called "balancing". Instead, the result of a shift always have the type of the promoted left operand. In this case int.
  • So a gets promoted to type int, still containing the value 0x05. The 7 literal was already of type int so it doesn't get promoted.
  • When you left shift this int by 7, you get 0x0280. The result of the operation is of type int.
  • Note that int is a signed type, so had you kept shifting data further, into the sign bits, you would have invoked undefined behavior. Similarly, had either the left or the right operand been a negative value, you would also invoke undefined behavior.
  • You now have the expression a = 0x280 >> 7;. No promotions take place for the next shift operation, since both operands are already int.
  • The result is 5 and of the type int. You then convert this int to an unsigned char, which is fine, since the result is small enough to fit.

Case b:

  • b <<= 7; is equivalent to b = b << 7;.
  • As before, b gets promoted to an int. The result will again be 0x0280.
  • You then attempt to store this result in an unsigned char. It will not fit, so it will get truncated to only contain the least significant byte 0x80.
  • On the next line, b again gets promoted to an int, containing 0x80.
  • And then you shift 0x80 by 7, getting the result 1. This is of type int, but can fit in an unsigned char, so it will fit in b.

Good advice:

  • Never ever use bit-wise operators on signed integer types. This doesn't make any sense in 99% of the cases but can lead to various bugs and poorly defined behavior.
  • When using bit-wise operators, use the types in stdint.h rather than the primitive default types in C.
  • When using bit-wise operators, use explicit casts to the intended type, to prevent bugs and unintended type changes, but also to make it clear that you actually understand how implicit type promotions work, and that you didn't just get the code working by accident.

A better, safer way to write your program would have been:

#include <stdio.h> #include <stdint.h>      int main(void) {     uint8_t a=0x05;     uint8_t b=0x05;     uint32_t tmp;      // first operation     tmp = (uint32_t)a << 7;     tmp = tmp >> 7;     a = (uint8_t)tmp;      // second operation     tmp = (uint32_t)b << 7;     tmp = tmp >> 7;     b = (uint8_t)tmp;      printf("a=%X b=%X\n", a, b);     return 0; }  
like image 26
Lundin Avatar answered Sep 30 '22 05:09

Lundin