C++ safety
C++ integer overflow: why it happens and how to check for it
An integer type can represent only a finite range of whole numbers. Integer overflow describes an operation whose mathematical result is outside that range. In C++, signed overflow has undefined behavior: the language does not require a wrapped value or any other result. The program cannot safely test the result afterward because evaluating the overflowing expression already invokes undefined behavior.
Know the type's range
Every integer type has a minimum and maximum representable value. The exact width of plain int depends on the implementation, so do not hard-code a boundary unless your program has an explicit platform requirement. Include <limits> and ask std::numeric_limits<int>::min() or max() for the boundaries of the type you use.
The destination type matters. An expression is evaluated using the types of its operands before assignment. Assigning the result to a wider variable does not automatically widen the operation. Convert an operand before the operation only when the chosen wider type can hold every possible result.
#include <iostream>
#include <limits>
int main() {
const bool supportsLargeCount =
std::numeric_limits<int>::max() > 100000;
std::cout << std::boolalpha << supportsLargeCount << '\n';
return 0;
}
Expected output on a platform where int can represent values above 100000:
true
This program asks about the implementation instead of assuming that all systems use the same integer width. For a portable test, also consider the platforms your application supports. The C++ standard guarantees minimum ranges, not that every integer type has an identical size everywhere.
Check before adding
For addition, compare an operand with a safe boundary before evaluating the sum. If the second operand is positive, the first must be no greater than maximum - second. If the second operand is negative, the first must be no less than minimum - second. These subtractions stay in range when the sign branch is correct.
Do not compute the sum and then compare it with the maximum. That test depends on an operation that may already have undefined behavior. Similarly, do not rely on a cast after an overflowing addition. The cast only converts the result; it cannot undo the earlier evaluation.
#include <iostream>
#include <limits>
int main() {
const int current = 40;
const int maximum = std::numeric_limits<int>::max();
if (current == maximum) {
std::cout << "limit\n";
} else {
std::cout << current + 1 << '\n';
}
return 0;
}
Expected output:
41
Before incrementing, this program checks that the current value is not already at the upper bound. In a function that accepts arbitrary values, include the same check there. If the operation can subtract or add a negative operand, split the checks by sign rather than applying only the positive case.
Understand unsigned arithmetic
Unsigned integer arithmetic is defined modulo one more than the type's maximum value. A result that passes the maximum wraps into the lower part of that range. This behavior can be useful for bit-level operations, but it is usually a poor substitute for checking a count, balance, size, or duration. The resulting value may look valid while representing the wrong quantity.
Conversions between signed and unsigned types can also change the value. A negative signed value converted to an unsigned type becomes a value determined by the unsigned range. Comparisons between signed and unsigned values may convert one operand before comparing. Review the operand types when a boundary check behaves unexpectedly.
Multiplication needs checks that account for both operand signs. Division-based bounds can help, but division by zero and the minimum signed value divided by negative one need separate handling. A wider intermediate can simplify the check only if it is guaranteed to hold the product. For general untrusted values, use a tested checked-arithmetic routine or define and enforce narrower input limits.
Input parsing has its own range failure. Extracting a number that is too large for the destination type sets the stream's failure state. Check extraction before performing arithmetic, and handle that failure as invalid input. A successful read only proves that the value fits the input type, not that a later calculation fits.
Exercise
Read a whole number of hours and convert it to seconds in an int. Print Out of range if the multiplication would exceed the range of int. Sample input:
700000
Expected output on a platform with a 32-bit int:
Out of range
Hint 1: Divide the destination limit by 3600 before multiplying. Hint 2: Check extraction before the range check.
Show one possible solution
#include <iostream>
#include <limits>
int main() {
long long hours = 0;
if (!(std::cin >> hours)) {
return 1;
}
const long long maximum = std::numeric_limits<int>::max();
const long long minimum = std::numeric_limits<int>::min();
if (hours > maximum / 3600 || hours < minimum / 3600) {
std::cout << "Out of range\n";
return 0;
}
const int seconds = static_cast<int>(hours * 3600);
std::cout << seconds << '\n';
return 0;
}
Related guides
Keep learning with CppPath
Continue with the Integer Overflow & Safe Arithmetic lesson. The full lesson includes a quiz and a checked coding challenge with a free account.