22.9. What happens if new fails?

[ fromfile: newfailure.xml id: newfailure ]

[Caution]Prerequisites

Every book on C++ has a section on handling new failures. The accepted wisdom for how to handle such failures tends to vary, because the behavior of a C++ program when it runs out of memory is not the same from one platform to another.

We begin our discussion with a caveat. When a C++ program has a memory leak and runs for a long time, eventually there will be no memory available to it. You might think that would cause an exception to be thrown. However most modern operating systems (including *nix and Win32) implement virtual memory, which permits the operating system, when its random access memory (RAM) fills up beyond some preset level, to copy the contents of memory that has not been used recently to a special place on the system disk drive. This substitution of relatively slow memory (disk storage) for fast memory (RAM) is generally invisible to the user (except for the performance degradation). If the demands on the system RAM are especially heavy, the OS will use virtual memory to keep satisfying allocation requests until the system starts thrashing[70]. When this happens, the whole system grinds to a halt until the system administrator can intervene and kill the memory-eating process. At no point will any of the memory allocation failure-handling code be reached in the errant process. It is for this reason that memory allocation errors are handled differently, or not at all, in various applications.

Having said this, the ANSI/ISO standard does specify that the free store operator new should to throw a bad_alloc exception instead of returning NULL if it cannot carry out an allocation request. If a thrown bad_alloc exception is not caught by a catch() block, the “default exception handler” is called, which could be either abort() or terminate().

Example 22.7 demonstrates this feature of C++.

Example 22.7. src/newfailure/bad-alloc1.cpp

#include <iostream>
#include <new>
using namespace std;

void memoryEater() {
    int i = 0;
    double* ptr;
    try {
        while (1) {
            ptr = new double[50000000];
            cerr << ++i << '\t' ;
        }
    } catch (bad_alloc& excpt) {
        cerr << "\nException occurred: "
        << excpt.what() << endl;
    }
}

int main() {
    memoryEater();
    cout << "Done!" << endl;
    return 0;
}

Output:

src/newfailure> g++ bad-alloc1.cpp src/newfailure> ./a.out 1 2 3 4 5 6 7 Exception occurred: St9bad_alloc Done! src/newfailure>

[Important]Question

Why were we able to reach the exception handling code in this example, without causing any thrashing?



[70] When a system is constantly swapping memory back and forth to disk, preventing other I/O from happening, we call that “thrashing.“