How std::bad_alloc Notifies Heap Exhaustion in C++
This article explains how the C++ runtime uses the
std::bad_alloc exception to signal heap allocation failures
to calling contexts. When system memory is exhausted during dynamic
allocation, C++ bypasses traditional error-return codes in favor of an
exception-driven mechanism that initiates stack unwinding. Readers will
learn the internal process triggered by operator new, the
role of std::new_handler, how stack unwinding ensures
resource cleanup, and how calling functions can effectively intercept
and handle this failure.
The Trigger: Dynamic Memory Allocation Failure
In C++, dynamic memory is predominantly requested using the
new expression. Under the hood, this invokes
::operator new to allocate raw bytes from the free store
(heap). If the operating system or runtime memory manager cannot fulfill
the requested byte size, allocation fails.
Before immediately throwing an exception, the runtime checks for a
registered std::new_handler function. If one exists, it is
called in an attempt to free memory. If no handler is registered, or if
the handler fails to free memory and returns,
::operator new halts execution of the constructor that
would have followed and instantiates a std::bad_alloc
object, which is defined in the <new> header and
derives from std::exception.
Notification via Stack Unwinding
Unlike C's malloc(), which notifies callers by returning
a nullptr, standard C++ new uses structured
exception handling. Once std::bad_alloc is thrown, normal
sequential execution halts immediately.
The C++ runtime initiates stack unwinding to notify the calling context:
- Scope Traversal: The runtime searches backward up
the call chain, leaving the current scope and evaluating outer scopes
for an active
try...catchblock. - Deterministic Cleanup (RAII): As the runtime exits each stack frame during unwinding, it calls the destructors for all fully constructed local objects in reverse order of their creation. This guarantees that stack-allocated resources (such as file handles, locks, and other smart pointers) are safely released despite the out-of-memory condition.
- Target Match: The unwinding continues until it
finds a
catchclause matchingstd::bad_allocor one of its base classes, such asstd::exception.
Interception in Calling Contexts
Calling contexts intercept the heap exhaustion notification by
wrapping allocation sites in a try block and defining an
appropriate handler:
#include <iostream>
#include <new>
void allocateMemory() {
// Attempting to allocate an impossibly large block
size_t hugeSize = -1;
int* ptr = new int[hugeSize];
delete[] ptr;
}
int main() {
try {
allocateMemory();
} catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << '\n';
// Handle failure or gracefully degrade functionality
}
return 0;
}If the calling context catches
const std::bad_alloc&, it receives direct notification
that memory ran out, allowing the program to log the error, release
non-essential caches, or terminate cleanly.
Uncaught Exceptions and Termination
If no calling context up the entire call hierarchy catches the
std::bad_alloc exception, the stack unrolls to the top
level (typically main()). If left uncaught, the runtime
calls std::terminate(). By default,
std::terminate invokes std::abort(),
immediately ending process execution without completing further resource
destruction, thereby protecting system stability from corrupted or
incomplete program states.