Why do the upper 32 bits of a uint64_t become one whilst performing a specific bitwise operation?

3

Can someone please explain to me why the upper 32 bits of a uint64_t are set to one in case number #2:

uint64_t ret = 0;
ret = (((uint64_t)0x00000000000000FF) << 24);
printf("#1 [%016llX]\n", ret);

ret = (0x00000000000000FF << 24);
printf("#2 [%016llX]\n", ret);

uint32_t ret2 = 0;
ret2 = (((uint32_t)0x000000FF) << 8);
printf("#3 [%08X]\n", ret2);

ret2 = (0x000000FF << 8);
printf("#4 [%08X]\n", ret2);

Output:

#1 [00000000FF000000]
#2 [FFFFFFFFFF000000]
#3 [0000FF00]
#4 [0000FF00]

https://ideone.com/xKUaTe

You'll notice I've given an "equivalent" 32bit version (cases #3 and #4) which doesn't show the same behaviour...

c
literals
signed
integer-promotion
uint64
asked on Stack Overflow Oct 14, 2015 by Stuart Gillibrand • edited Apr 7, 2019 by phuclv

1 Answer

3

By default integer literals without a suffix will have type int if they fit in an int.

The type of the integer constant

The type of the integer constant is the first type in which the value can fit, from the list of types which depends on which numeric base and which integer-suffix was used.

  • no suffix
  • decimal bases:
    • int
    • long int
    • unsigned long int (until C99)
    • long long int (since C99)
  • binary, octal, or hexadecimal bases:
    • int
    • unsigned int
    • long int
    • unsigned long int
    • long long int (since C99)
    • unsigned long long int (since C99)
  • ...

As a result 0x00000000000000FF will be an int regardless of how many zeros you put in. You can check that by printing sizeof 0x00000000000000FF

Therefore, 0x00000000000000FF << 24 results in 0xFF000000 which is a negative value1. That'll again be sign extended when casting to uint64_t, filling the top 32 bits with ones

Casting help, as you can see in (uint64_t)0x00000000000000FF) << 24, because now the shift operates on the uint64_t value instead of int. You can also use a suffix

0x00000000000000FFU << 24
0x00000000000000FFULL << 24

The first line above does the shift in unsigned int and then do zero extension to cast to uint64_t. The second one does the operation in unsigned long long directly

0x000000FF << 8 doesn't expose the same behavior because the result is 0xFF00 which doesn't have the sign bit set, but it will if you do (int16_t)0x000000FF << 8

There are a lot of related and duplicate questions:


1 Technically shifting into the sign bit results in undefined behavior but in your case the compiler has chosen to leave the result the same as when you shift an unsigned value: 0xFFU << 24 = 0xFF000000U which when converted to signed produces a negative value

See

answered on Stack Overflow Sep 28, 2018 by phuclv • edited Oct 21, 2020 by phuclv

User contributions licensed under CC BY-SA 3.0