当内存耗尽时,operator new不会返回0。

3

我在Bruce Eckel的《Thinking in C++,第2版,卷1》第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);
  }
}

我的问题是:为什么这段代码在完全耗尽内存时(根据Win7的资源监视器),不会抛出bad_alloc异常呢?我假设全局的::new char[sz]从未返回0,即使内存已满。但是为什么呢?甚至一旦内存用尽,它将使Win7操作系统变得麻痹、无响应,但它仍然继续尝试分配新空间。
(有一个有趣的补充:我也在Ubuntu上尝试了它:也没有抛出bad_alloc异常,但这个危险的进程在被操作系统杀死之前,不进行冻结处理——很聪明,不是吗?)

1
[OT] 已经在这里解释过,Windows任务管理器不显示真实的资源使用情况。 - Victor Polevoy
内存耗尽意味着你已经分配了所有的RAM,并且没有剩余空间来扩展交换文件。这不仅适用于Windows,而是适用于所有操作系统。 - Panagiotis Kanavos
1
如果您使用的是64位Windows,并且拥有超过4GB的RAM,您也可以尝试构建32位应用程序。 - Mats Petersson
1
http://en.cppreference.com/w/cpp/memory/new/nothrow - Marc Glisse
@Janos 不行,这会导致 OOM Killer 杀死进程,因此结果是一样的。无论是使用 new (noexcept version) 还是 malloc,都会出现相同的行为,因为操作系统总是会处理它们。您可以尝试通过 ulimit 限制应用程序的内存访问,并且我相信这将允许您的应用程序达到预期的效果。否则,请阅读 OOM Killer 策略。 - Victor Polevoy
显示剩余2条评论
2个回答

2
您的 operator new 实现不正确。
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 已经抛出 std::bad_alloc,你不需要检查 p 指针的返回值。

如果你查看 g++libstdc++ 源代码,它们在使用 malloc 后将指针与 null 进行比较,因此为了模拟这一点,你也应该这样做:

"
_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;
}

因此,它不返回0而是抛出一个exception。我相信你在Linux上没有得到它的原因是,在这种情况下,进程总是被内核(OOM-Killer)杀死。
正如@MarcGlisse所指出的,您可能想使用newnothrow(noexcept)版本:
_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;
}

你会发现,如果分配失败,它将返回0,并捕获由new_handler引发的所有异常。默认的new_handler会抛出std::bad_alloc异常。 但即使在这种情况下,我认为OOM-Killer也会在你得到任何结果之前杀死你的应用程序。如果你的问题更多地涉及“为什么被杀死?”,那么我建议你阅读关于OOM killer策略的相关内容。

1
我发现了我的错误:我错误地调用了newnew NoMemory(); 正确的方法是new NoMemory;(不带括号) 现在它可以像魔术一样正常工作,就像这样:
#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);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接