A painting of me

Testing Memory Allocation Failure

   15 December 2004, evening time

When programming in C a programmer usually uses the malloc function to request memory on the heap. In C++ one can use malloc, but generally the proper thing to do is use new, and for arrays new[]. The new operator, when successful, returns a block of memory big enough for the object or array one wishes to store. Unfortunately, sometimes new and malloc will fail to find any memory.

Now, in C the proper thing to do is check that the memory was allocated before an attempt to use it. malloc returns 0 when it fails to find enough free memory. The common idiom in C then is to test if the value of the pointer pointing to the memory requested is NULL (0) after a call to malloc.

  1. ...
  2. int* my_int_ptr = malloc( sizeof(int) );
  3. if ( 0 == my_int_ptr ) {
  4. // handle error
  5. }
  6. ...
  7. Download this code: /static/code/9.txt

I was vaguely aware that in C++ things were different, but didn’t concern myself with learning exactly how different till today. If a computer fails to allocate memory after a call to new or malloc, chances are the computer is on the verge of crashing. As such, for most programs a reasonable course of action when a new or malloc fails is to simply terminate. However, there are many applications where this is not a reasonable thing to do. When faced with writing such an application, it helps to know what will happen when new fails.

A normal call to new throws a bad_alloc exception. If this is not caught, it will get transformed into a std::terminate() call. std::terminate() by default calls abort(), which exits the running program and does not run destructors for static objects. This is quite different from malloc. The following program will crash (on my local machine the program outputs ‘Aborted’):

  1. #include <iostream>
  2. int main() {
  3. const int n = 0x7FFFFFFF;
  4. for (;;) {
  5. char* memory = new char[n];
  6. if ( 0 == memory ) {
  7. std::cerr << "Operator new returns a NULL";
  8. } else {
  9. memory[0] = 'A';
  10. memory[n-1] = 'B';
  11. std::cout << "A-OK";
  12. }
  13. }
  14. return 0;
  15. }
  16. Download this code: /static/code/6.txt

Testing for NULL will accomplish nothing, the program will still crash. To have new behave like malloc when it fails one must call the overloaded version of new, new(nothrow). Doing so will result in new not throwing an exception and returning NULL if it fails. This program will print “Operator new returns NULL” forever:

  1. #include <iostream>
  2. int main() {
  3. const int n = 0x7FFFFFFF;
  4. for (;;) {
  5. CHAR* memory = new(std::nothrow) char[n];
  6. if ( 0 == memory ) {
  7. std::cerr << "Operator new returns a NULL";
  8. } else {
  9. memory[0] = 'A';
  10. memory[n-1] = 'B';
  11. std::cout << "A-OK";
  12. }
  13. }
  14. return 0;
  15. }
  16.  
  17. Download this code: /static/code/7.txt

By default new uses the advanced erorr handling capabilities of C++. As such, using new(nothrow) is really not a good solution when one needs to write a robust program. One should make it a point to catch the bad_alloc exception a call to new may throw, and handle it appropriately. This program will print “New throws a bad_alloc exception” forever:

  1. #include <iostream>
  2. int main() {
  3. const int n = 0x7FFFFFFF;
  4. for (;;) {
  5. try {
  6. char* memory = new char[n];
  7. memory[0] = 'A';
  8. memory[n-1] = 'B';
  9. std::cout << "A-OK";
  10. }
  11. catch (std::bad_alloc)
  12. {
  13. std::cerr << "New throws a bad_alloc exception";
  14. }
  15. }
  16. return 0;
  17. }
  18. Download this code: /static/code/8.txt

And that is more then you probably wanted to know about new.

|  

Comments

  1. Cool. What happens in Java?

  2. You read my mind. I was thinking about that on the bus ride home, but haven’t had a chance to look into it yet. I suspect that the JVM will probably raise an exception.

  3. Java does indeed throw an exception on a failure to allocate memory: OutOfMemoryError.

  4. Can you tune if up, like C++, though? Throwing an exception seems like the best way to go though.

  5. Question: We allocated two dynamic heaps, one of which can be destroyed (lets call it heap2). We run out of memory when trying to allocate for heap1. We then destroy heap2 and have lots of memory available again. But when we try to allocate for heap 1 again, it still thinks there is no memory available.

    We are however able to create a new heap and allocate. It is just the existing heap that refuses to grow.

    What stops the allocate?

  6. Further on the above question: We are able to allocate massive amounts of memory to the new heap (e.g. 2.1 GB). It is just the existing heap that refuses to grow even though it was created with an initial size 0 i.e. a growable heap.

  7. I have no clue, based on your two comments. What OS is this all taking place on? When you say Dynamic Heap, do you mean an STL heap?

Don't be shy, you can comment too!

 
Some things to keep in mind: You can style comments using Textile. In particular, *text* will get turned into text and _text_ will get turned into text. You can post a link using the command "linktext":link, so something like "google":http://www.google.com will get turned in to google. I may erase off-topic comments, or edit poorly formatted comments; I do this very rarely.