C++ Implementation Safety
Integer overflow, signed/unsigned comparisons, iterator invalidation, references to temporary objects, recursion depth, stack/heap memory, comparator correctness, undefined behavior, uninitialized data, and sanitizers.
Output (g++ 11.2, -std=c++17, warnings as errors, AddressSanitizer + UndefinedBehaviorSanitizer; Ubuntu 22.04 on WSL2):
Signed overflow is undefined behaviour: compilers assume it never happens and may optimise accordingly. Widen first (1LL * x * y) or use checked builtins. -fsanitize=undefined catches it at run time.
Signed/unsigned mixing: v.size() - 1 on an empty vector wraps to SIZE_MAX. Write i + 1 < v.size() or cast sizes to a signed type once.
Iterator invalidation: vector::push_back may reallocate (Module 4); erase invalidates iterators at and after the erased element — use the erase–remove idiom or the iterator returned by erase.
Dangling references: binding a temporary directly to a const& local extends its lifetime, but returning a reference to a local or to a temporary does not.
Recursion depth: recursion on 10⁶-deep structures can overflow the stack — convert to iteration (Modules 11, 29).
Comparators: must be a strict weak ordering (Module 4).
Uninitialised data: reading an uninitialised local is undefined behaviour; value-initialise with {}.
Sanitizers: AddressSanitizer (out-of-bounds, use-after-free, leaks) and UndefinedBehaviorSanitizer (overflow, bad shifts, misaligned access) — every program in this course passed both.
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> empty;
cout << "empty.size() - 1 = " << empty.size() - 1 << " (unsigned wrap: `for (i = 0; i < v.size() - 1; ++i)` would run ~1.8e19 times)\n";
cout << "safe form: i + 1 < v.size() -> loop runs " << [&] { int n = 0; for (size_t i = 0; i + 1 < empty.size(); ++i) ++n; return n; }() << " times\n";
int a = INT_MAX, b = 1, sum;
if (__builtin_add_overflow(a, b, &sum)) cout << "INT_MAX + 1 detected as overflow before it happens (checked arithmetic)\n";
long long prod = 1LL * 100000 * 100000; // widen before multiplying
cout << "100000 * 100000 computed in long long = " << prod << '\n';
vector<int> v = {1, 2, 3, 4, 5, 6};
v.erase(remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }), v.end()); // erase-remove: no invalidated iterators
cout << "after removing evens safely:"; for (int x : v) cout << ' ' << x;
cout << '\n';
const string& ref = string("temporary"); // lifetime extended: binding a temporary to const& directly is safe
cout << "const& to a temporary bound directly: \"" << ref << "\" (but returning such a r
…empty.size() - 1 = 18446744073709551615 (unsigned wrap: `for (i = 0; i < v.size() - 1; ++i)` would run ~1.8e19 times)
safe form: i + 1 < v.size() -> loop runs 0 times
INT_MAX + 1 detected as overflow before it happens (checked arithmetic)
100000 * 100000 computed in long long = 10000000000
after removing evens safely: 1 3 5
const& to a temporary bound directly: "temporary" (but returning such a reference from a function is not)
value-initialised int: 0Quiz
? What is `empty.size() - 1` for an empty std::vector on a 64-bit system?
+ 18446744073709551615 (SIZE_MAX) :: Unsigned arithmetic wraps; the run printed it.
- -1 :: size_t is unsigned.
- 0 :: No.
- Undefined behaviour :: Unsigned wrap is defined; the loop using it is the bug.
? Which tool reports signed integer overflow at run time?
+ UndefinedBehaviorSanitizer (-fsanitize=undefined) :: Used for every program in this course.
- AddressSanitizer alone :: Memory errors, not overflow.
- -O2 :: Optimisation can make overflow bugs worse.
- valgrind memcheck :: Memory errors.
? Which are undefined behaviour in C++? (select all that apply)
+ Signed integer overflow :: Yes.
+ Reading an uninitialised local int :: Yes.
- Unsigned integer wrap-around :: Defined modulo 2ⁿ.
+ Using an iterator invalidated by reallocation :: Yes.