How is shift operator evaluated in C?

后端 未结 3 1736
清歌不尽
清歌不尽 2021-01-01 11:53

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

To explain it, let me write this small run

相关标签:
3条回答
  • 2021-01-01 12:17

    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

    0 讨论(0)
  • 2021-01-01 12:24

    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:

    1. Each of the operands shall have integer type.
    2. 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.

    0 讨论(0)
  • 2021-01-01 12:29

    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;
    } 
    
    0 讨论(0)
提交回复
热议问题