Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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...

like image 539
Stuart Gillibrand Avatar asked Sep 18 '25 14:09

Stuart Gillibrand


1 Answers

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:

  • Type of integer literals not int by default?
  • What Are Integer Literal Type? And How They Are Stored?
  • what is the reason for explicitly declaring L or UL for long values
  • ...

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

  • Bitwise shift operation in C on uint64_t variable
  • Why does shifting 0xff left by 24 bits result in an incorrect value?
like image 158
phuclv Avatar answered Sep 21 '25 14:09

phuclv