Janos
Janos

Reputation: 720

operator new doesn't return 0 when running out of memory

I found this interesting exercise in Bruce Eckel's Thinking in C++, 2nd ed., Vol.1 in Chapter 13:

/*13. Modify NoMemory.cpp so that it contains an array of int
and so that it actually allocates memory instead of
throwing bad_alloc. In main( ), set up a while loop like
the one in NewHandler.cpp to run out of memory and
see what happens if your operator new does not test to
see if the memory is successfully allocated. Then add the
check to your operator new and throw bad_alloc*/

#include <iostream>
#include <cstdlib>
#include <new> // bad_alloc definition
using namespace std;

int count = 0;


class NoMemory {
  int array[100000];
public:
  void* operator new(size_t sz) throw(bad_alloc)
  {
    void* p = ::new char[sz];
    if(!p)
    {
      throw bad_alloc(); // "Out of memory"
    }
    return p;
  }
};


int main() {

  try {
    while(1) {
      count++;
      new NoMemory();
    }
  }
  catch(bad_alloc)
  {
    cout << "memory exhausted after " << count << " allocations!" << endl;
    cout << "Out of memory exception" << endl;
    exit(1);
  }
}

My question is: why does this code does not throw the bad_alloc, when ran completely out of memory (as per the Task Manager's resource monitor on Win7)? I assume the global ::new char[sz] never returns with 0, even if the memory is full. But why? It even turns the Win7 OS into a numb, non-responding state once ran out of memory, still is does keep on trying to allocate new space.

(One interesting addition: I tryed it on Ubuntu too: the bad_alloc is not thrown either, still this OS does not goes frozen but this dangerous process gets killed before by the OS - smart isn't it?)

Upvotes: 3

Views: 1687

Answers (2)

Janos
Janos

Reputation: 720

I've found my error: I called new wrong: new NoMemory(); The correct way to do is new NoMemory; (without the parentheses)

Now it works like a charm, like this:

#include <iostream>
#include <cstdlib>
#include <new> // bad_alloc definition
using namespace std;

int count = 0;

class NoMemory {
  int array[100000];
public:

  void* operator new(size_t sz) throw(bad_alloc)
  { 
    void* p = ::new(std::nothrow) char[sz];
    if(0 != p)
      return p;
    throw bad_alloc();
  }
};


int main() {
  try {
    while(1) {
      count++;
      new NoMemory;
    }
  }
  catch(bad_alloc)
  {
    cout << "memory exhausted after " << count << " allocations!" << endl;
    cout << "Out of memory exception" << endl;
    exit(1);
  }
}

Upvotes: 1

VP.
VP.

Reputation: 16755

Your implementation of operator new is incorrect.

void* operator new(size_t sz) throw(bad_alloc)
{
  void* p = ::new char[sz];
  if(!p)
  {
    throw bad_alloc(); // "Out of memory"
  }
  return p;
}

::new already throws std::bad_alloc and you don't need to check return value of p pointer.

If you look g++'s libstdc++ source, they compare pointer to null after malloc, so you should do that too in order to simulate this:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
  void *p;

  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;

  while (__builtin_expect ((p = malloc (sz)) == 0, false))
    {
      new_handler handler = std::get_new_handler ();
      if (! handler)
        _GLIBCXX_THROW_OR_ABORT(bad_alloc());
      handler ();
    }

  return p;
}

So it does not return 0 but throws an exception. The reason why don't you get it on linux I believe is that the process is always killed by the kernel (OOM-Killer) in such cases.

As @MarcGlisse pointed you may want to use nothrow (noexcept) version of new:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz, const std::nothrow_t&) GLIBCXX_USE_NOEXCEPT
{
  void *p;

  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;

  while (__builtin_expect ((p = malloc (sz)) == 0, false))
    {
      new_handler handler = std::get_new_handler ();
      if (! handler)
        return 0;
      __try
        {
          handler ();
        }
      __catch(const bad_alloc&)
        {
          return 0;
        }
    }

  return p;
}

As you see it will return 0 if allocation fails and it will catch all exceptions that can be raised by new_handler. Default new_handler throws std::bad_alloc. But even in this case I think OOM-Killer will kill your application before you get something. If your question is more about why is it killed? then I recommend you to read about OOM killer policy.

Upvotes: 2

Related Questions