C++ gives you direct access to memory, which means you have to choose exactly how many bytes of RAM your variables use. If you select a type that is too small, your values will overflow. If you choose one that is too large, you waste cache space.
Let's break down the basic primitive types in C++, their typical sizes, and the systems hazards you need to watch out for.
Primitive Types and Byte Widths
Unlike languages where all numbers are 64-bit floats under the hood, C++ has specific integer and floating point types:
* char: 1 byte (stores characters or small integers from -128 to 127).
* short: 2 bytes (integers from -32,768 to 32,767).
* int: 4 bytes (integers from -2.1 billion to 2.1 billion).
* long: Typically 4 bytes on Windows, but 8 bytes on macOS and Linux (which is a common source of porting bugs!).
* long long: 8 bytes (integers from -9 quintillion to 9 quintillion).
* float: 4 bytes (single precision floating point).
* double: 8 bytes (double precision floating point).
The Signed vs Unsigned Comparison Hazard
In C++, integers can be signed (can store positive and negative numbers) or unsigned (can only store positive numbers and zero).
Here is a critical trap: never mix signed and unsigned integers in comparisons. If you compare them, C++ will implicitly convert the signed integer to an unsigned integer. This leads to silent logic bugs:
Why does this happen? Because -5 in binary is represented in two's complement. When converted to an unsigned int, it becomes 4,294,967,291 on 32-bit systems, which is obviously much larger than 2!
Integer Overflow and Underflow
What happens if you try to store a number that exceeds a variable's maximum capacity?
C++ handles this in two different ways depending on whether the type is signed or unsigned:
1. Unsigned Overflow (Defined Wrap around)
For unsigned integers, overflow is fully defined by the C++ standard. It wraps around using modulo arithmetic. If you add 1 to the maximum unsigned value, it goes back to 0:
2. Signed Overflow (Undefined Behavior)
For signed integers, overflow is undefined behavior (UB). The C++ standard makes no guarantees about what happens. The hardware might wrap it around to a negative number, freeze, or the compiler might optimize away your overflow checks entirely because it assumes signed overflow never happens.
To write safe systems code, always check your values before performing arithmetic if they are close to their limits.
Floating Point Epsilon Comparison Hazards
Unlike integers, floating point numbers (like float and double) cannot represent decimals exactly in base-2 binary format. For example, adding 0.1 and 0.2 in C++ results in 0.30000000000000004 rather than exactly 0.3.
Because of this tiny precision loss, comparing floating point numbers directly using the double-equals operator == is a critical systems bug. The check will almost always return false:
To safely compare floating point variables, you must check if the absolute difference between them is smaller than a tiny tolerance value called Epsilon: