Let's break your problem up into two pieces. The first is the shift operation, and the other is the conversion to uint64_t
.
As far as the left shift is concerned, you are invoking undefined behavior on 32-bit (or smaller) architectures. As others have mentioned, the operands are int
. A 32-bit int
with the given value would be 0x000000ff
. Note that this is a signed number, so the left-most bit is the sign. According to the standard, if you the shift affects the sign-bit, the result is undefined. It is up to the whims of the implementation, it is subject to change at any point, and it can even be completely optimized away if the compiler recognizes it at compile-time. The latter is not realistic, but it is actually permitted. While you should never rely on code of this form, this is actually not the root of the behavior that puzzled you.
Now, for the second part. The undefined outcome of the left shift operation has to be converted to a uint64_t
. The standard states for signed to unsigned integral conversions:
If the destination type is unsigned, the resulting value is the smallest unsigned value equal to the source value modulo 2n where n is the number of bits used to represent the destination type.
That is, depending on whether the destination type is wider or narrower, signed integers are sign-extended[footnote 1] or truncated and unsigned integers are zero-extended or truncated respectively.
The footnote clarifies that sign-extension is true only for two's-complement representation which is used on every platform with a C++ compiler currently.
Sign-extension means just that everything left of the sign bit on the destination variable will be filled with the sign-bit, which produces all the f
s in your result. As you noted, you could left shift 0x7f
by 3-bytes without this occurring, That's because 0x7f=0b01111111
. After the shift, you get 0x7f000000
which is the largest signed int, ie the largest number that doesn't affect the sign bit. Therefore, in the conversion, a 0
was extended.
Converting the left operand to a large enough type solves this.
uint64_t temp = uint64_t(0xff) << 24