Why the output of printf("%llu\n", 1ull << n);
and printf("%llu\n", 1ull << 64);
is different in C++?
Code:
#include <cstdio>
int main()
{
int n = 64;
printf("%llu\n", 1ull << n);
printf("%llu\n", 1ull << 64);
return 0;
}
Output:
1
0
It works the same way as “printf” works in programming languages like C. Note: printf can have format specifiers, escape sequences or ordinary characters. Format Specifiers: The most commonly used printf specifiers are %s, %b, %d, %x and %f.
Now, printf always returns an integer value, which is the number of characters it successfully printed. In this case, that call to printf will return 6, the number of characters in the string “printf”. So, the data type of the second argument to the outer printf is an int, and the value of that argument will be 6.
Note − It does not print anything. Another printf () function is used to print the statement. Even if we give the value to the identifier, it will not consider the value given by us.
Now, printf always returns an integer value, which is the number of characters it successfully printed. In this case, that call to printf will return 6, the number of characters in the string “printf”.
See here:
In any case, if the value of the right operand is negative or is greater or equal to the number of bits in the promoted left operand, the behavior is undefined.
If your left operand is 64 bit and your right operand is 64
, this is undefined behavior, and then anything can happen without any guarantee of consistency.
Your compiler should also issue a warning for that, at least it does for me when I try it with GCC or Visual Studio.
The reason is that expressions like 1<<64
are compile time constants and are indeed computed by the compiler at compile time. No code to shift anything is emitted.
The expression 1<<64
is evaluated by the compiler as 0
, which is plausible and legit since the behavior is actually, as others have pointed out, undefined. The produced assembly for uint64_t i = (uint64_t)1 << 64;
is simply to store zero in the variable's location:
QWORD PTR [rbp-16], 0
Now, for a non-compile time value code is emitted. uint64_t i2 = (uint64_t)1 << n;
translates to
mov rax, QWORD PTR [rbp-8]
mov edx, 1
mov ecx, eax
sal rdx, cl
mov rax, rdx
mov QWORD PTR [rbp-24], rax
All the boilerplate code before and after the actual SAL shift instruction is just moving the operands in place and moving the result into the variable. The important thing is that the compiler indeed emits code to shift the 1 here. Because shifting by more than 63 is illegal and pointless for 64 bit values Intel processors silently mask the shift value:
REX prefix in the form of REX.W [I must assume that that happens here] promotes operation to 64-bits and sets the mask width for CL to 6 bits.
That is, the processor internally masks n's value of 64/100'0000 with 63/11'1111, resulting in a shift value of 0. The result is, of course, the original 1.
With higher optimization levels the compiler optimizes that instruction away as well because it can infer the value of the non-volatile n
, and emits 0 there as well.
From the C Standard (6.5.7 Bitwise shift operators)
3 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
So the program has undefined behavior.
The difference of the output can be explained the following way that the compiler generates different object code when an integer constant (literal) is used compared with the code when a variable is used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With