0

Today, I read a code snippet and got confused how line 0 can work.

#define uint8_t unsigned char
#define uint16_t unsigned short

void func(void) {
    uint8_t pkt = 123;
    uint16_t value_0 = (uint16_t)(pkt << 8);  // line 0
    uint16_t value_1 = ((uint16_t)pkt) << 8;  // line 1
...
}

For line 0: per my understanding, uint16_t value_0 = (uint16_t)(pkt << 8); will be interpreted into below steps by compilers:

  1. generate a temp variable with type uint8_t to store the result of (pkt << 8), during which I thought the overflow happens and data is completely lost;
  2. cast the temp variable into uint16_t, which will be all zeros here;
  3. copy the value of this temp variable to the uint16_t value_1;

For line 1: per my understanding, uint16_t value_1 = ((uint16_t)pkt) << 8; will be interpreted into below steps by compilers:

  1. generate a temp variable with type uint16_t and store the value of pkt by casting it. In this step, the value is safely saved;
  2. save bit wise operation and save value copy;

But I tested the line 0 and line 1, they actually behave the same and both correctly. And I also checked the assembly codes: the assembly codes are the same from line 0 and line 1.

func():
        sub     sp, sp, #16
        mov     w0, 123
        strb    w0, [sp, 15]
        ldrb    w0, [sp, 15]
        and     w0, w0, 65535
        ubfiz   w0, w0, 8, 8
        strh    w0, [sp, 12]
        ldrb    w0, [sp, 15]
        and     w0, w0, 65535
        ubfiz   w0, w0, 8, 8
        strh    w0, [sp, 10]
        nop
        add     sp, sp, 16
        ret

There must be something I missed (maybe knowledge in the Compilers).

Could you please help me why line 0 and line behave the same? and which is preferred?

If possible can you help me refine the question title because I really don't know which knowledge I miss.

Thank you.


Answer this myself: did a new experiment: try to left bit shift 31 bits in the style line 0 and cast it to uint64_t. The printf shows the result is -2147483648, which means there is integer auto promotion during the cast with 32bits. 6.3.1.1 Boolean, characters, and integers

"If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.48) All other types are unchanged by the integer promotions."

#include <stdio.h>

#define uint8_t unsigned char
#define uint64_t unsigned long long int

int main(void)
{
    uint8_t pkt = 123;
    uint64_t value_0 = (uint64_t)(pkt << 31);  // line 0
    uint64_t value_1 = ((uint64_t)pkt) << 31;  // line 1
    printf("%lld %lld\n", value_0, value_1);
}

Output:

-2147483648 264140488704

leoleohu
  • 193
  • 1
  • 1
  • 10
  • 3
    IIrc, the C11 standard says that intermediate values of arithmetic that can fit in an int (for signed computations) or unsigned int (for signed) will be stored in those types. Since a `uint8_t` fits in `unsigned`, that's what's used here. The left shift operation result has that type. Most likely 32 or 64 bits. – Gene Jan 22 '22 at 06:34

0 Answers0