4

Source Code:

#include <iostream>
using namespace std;
int main() {
        unsigned long P;

        P = 0x7F << 24;
        cout << P << endl;

        P = 0x80 << 24;
        cout << P << endl;

        return 0;
}

Output:

2130706432
18446744071562067968

As you can see, the first result is correct. But the second result is extremely wrong. The expected result is 2147483648 and it does not match with 18446744071562067968.

I want to know why

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Akira Tsuchiya
  • 53
  • 1
  • 1
  • 8
  • 2
    `0x80ul << 24` https://godbolt.org/z/CvKqo8 – Marek R Feb 28 '20 at 16:03
  • 2
    Assuming an implementation with a 32-bit `int`, `0x80` has type `int` and so does the result of left-shifting it. It is not unsigned. `0x80 << 24` therefore overflows an `int` which gives undefined behaviour. – Peter Feb 28 '20 at 19:36

2 Answers2

10

The type of the expression 0x80 << 24 is not unsigned long, it’s int. You then assign the result of that expression to P, and in the process convert it to an unsigned long. But at that point it has already overflown (incidentally causing undefined behaviour). Use unsigned long literals in your expression:

P = 0x80ul << 24;
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
4

This problem is not entirely portable, since it depends on the number of bits in your representation of unsigned long. In this case, there is an overflow followed by an underflow, and the two effects combine to produce your surprising result.

The basic solution is indicated here: ULL suffix on a numeric literal

I've broken it down in the code below.


#include <iostream>
using namespace std;
int main() {

        cout << "sizeof(unsigned long) = " << sizeof(unsigned long) << "\n";
        cout << "sizeof(0x80) = " << sizeof(0x80) << "\n";

        int32_t a = (0x80 << 24); // overflow: positive to negative
        uint64_t b = a; // underflow: negative to positive

        uint64_t c = (0x80 << 24); // simple broken
        uint64_t d = (0x80UL << 24); // simple fixed
        uint32_t e = (0x80U << 24); // what you probably intended

        cout << "a = " << a << "\n";
        cout << "b = " << b << "\n";
        cout << "c = " << c << "\n";
        cout << "d = " << d << "\n";
        cout << "e = " << e << "\n";

}

Output:

$ ./c-unsigned-long-cannot-hold-the-correct-number-over-2-147-483-647.cpp 
sizeof(unsigned long) = 8
sizeof(0x80) = 4
a = -2147483648
b = 18446744071562067968
c = 18446744071562067968
d = 2147483648
e = 2147483648

If you're doing bit-shift operations like this, it probably makes sense to be explicit about the integer sizes (as I have shown in the code above).

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173