问题
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
回答1:
The shift operations would do integer promotions to its operands, and in your code the resulting int
is converted back to char
like this:
// first operation
a = ((a<<7)>>7); // a = (char)((a<<7)>>7);
// second operation
b <<= 7; // b = (char) (b << 7);
b >>= 7; // b = (char) (b >> 7);
Quote from the N1570 draft (which became the standard of C11 later):
6.5.7 Bitwise shift operators:
- Each of the operands shall have integer type.
- The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
And it's supposed that in C99 and C90 there are similar statements.
回答2:
In the first example:
a
is converted to anint
, shifted left, then right and then converted back tousigned char
.
This will result to a=5
obviously.
In the second example:
b
is converted toint
, shifted left, then converted back tounsigned char
.b
is converted toint
, shifted right, then converted back tounsigned char
.
The difference is that you lose information in the second example during the conversion to unsigned char
回答3:
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 typeint
, still containing the value 0x05. The7
literal was already of typeint
so it doesn't get promoted. - When you left shift this
int
by 7, you get 0x0280. The result of the operation is of typeint
. - 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 tob = b << 7;
.- As before,
b
gets promoted to anint
. 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;
}
来源:https://stackoverflow.com/questions/25989343/how-is-shift-operator-evaluated-in-c