What Happens If new Fails?
Section 21.4
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.[3] 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.
[3] When a system is constantly swapping memory back and forth to disk, preventing other I/O from happening, we call that "thrashing."
Having said this, the ANSI/ISO standard does specify that the free store operator new should 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 #include using namespace std; void memoryEater() { int i = 0; double* ptr; try { while(1) { ptr = new double[50000000]; cerr << ++i << ' ' ; } } catch (bad_alloc& excpt) { cerr << " Exception occurred: " << excpt.what() << endl; } } int main() { memoryEater(); <-- 1 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> (1)Try to use up the memory. |
22.9.1. set_new_handler(): Another Approach to New Failures
We can specify what new should do when there is not enough memory to satisfy an allocation request. When new fails, it first calls the function specified by set_new_handler(). If new_handler has not been set, a bad_alloc object is thrown that can be queried, as shown in Example 22.7, for more information by calling one of its member functions. Example 22.8 shows how to specify our own new_handler.
Example 22.8. src/newfailure/setnewhandler.cpp
#include #include #include using namespace std; void memoryEater() { int i = 0; double* ptr; while(1) { ptr = new double[50000000]; cerr << ++i << ' ' ; } } void out_of_store() { cerr << " operator new failed: out of store "; exit(1); } int main() { set_new_handler(out_of_store); memoryEater(); cout << "Done!" << endl; return 0; } Output: src/newfailure> g++ setnewhandler.cpp src/newfailure> ./a.out 1 2 3 4 5 6 7 operator new failed: out of store OOP> |
Note the absence of a TRy block.
Exercise: set_new_handler()Another Approach to new Failures
What happens if the last command in the out_of_store() function is not exit()? |
22.9.2. Using set_new_handler and bad_alloc
Example 22.9 throws a standard exception from the new_handler.
Example 22.9. src/newfailure/bad-alloc2.cpp
#include #include #include using namespace std; void memoryEater() { int i = 0; double* ptr; try { while(1) { ptr = new double[50000000]; cerr << ++i << ' ' ; } } catch(bad_alloc& excpt) { cerr << " Exception occurred: " << excpt.what() << endl; } } void out_of_store() { cerr << " operator new failed: out of store "; throw bad_alloc(); } int main() { set_new_handler(out_of_store); memoryEater(); cout << "Done!" << endl; return 0; } Output: src/newfailure> g++ bad-alloc2.cpp src/newfailure> ./a.out 1 2 3 4 5 6 7 operator new failed: out of store Exception occurred: St9bad_alloc Done! src/newfailure> |
22.9.3. Checking for null: The Updated Way to Test for New Failures
You may encounter the old null-checking style for detecting failures of new in legacy code. That's a sure sign that there are going to be problems with maintenance. Fortunately, there is a simple way to update that old approach.
In Example 22.10, we add the qualifier (nothrow) to the allocation statement. As its name suggests, this qualifier suppresses the throwing of bad_alloc and allows new to return a 0 pointer if it fails.
Example 22.10. src/newfailure/nullchecking.cpp
#include #include using namespace std; void memoryEater() { int i = 0; double* ptr; while(1) { ptr = new (nothrow) double[50000000]; if (ptr == 0) return; cerr << ++i << ' ' ; } } int main() { memoryEater(); cout << "Done!" << endl; return 0; } Output: src/newfailure> g++ nullchecking.cpp src/newfailure> ./a.out 1 2 3 4 5 6 7 Done! src/newfailure> |