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