9

这是我在学生时代问过自己的一个问题,但没有得到满意的答案,我一点一点地想出来了……直到今天。

我知道我可以通过检查返回的指针是否为 NULL 或处理 bad_alloc 异常来处理分配内存错误。

好的,但我想知道: new的调用如何以及为什么会失败?据我所知,如果空闲存储中没有足够的空间,分配内存可能会失败。但是现在这种情况真的会发生吗,有几 GB 的 RAM(至少在普通计算机上;我不是在谈论嵌入式系统)?我们是否还有其他可能发生分配内存失败的情况?

4

4 回答 4

19

尽管您已经获得了许多关于记忆为何/如何失败的答案但其中大多数都在某种程度上忽略了现实。

实际上,在真实系统上,这些论点中的大多数都没有描述事情的真正运作方式。尽管从这些是尝试的内存分配可能失败的原因的角度来看,它们是正确的,但从描述事物在现实中通常如何工作的角度来看,它们大多是错误的。

举个例子,在 Linux 中,如果您尝试分配比系统可用内存更多的内存,您的分配不会失败(即,您不会得到空指针或 strd::bad_alloc 异常)。相反,系统将“过度提交”,因此您会得到看似有效的指针——但是当/如果您尝试使用所有内存时,您将收到异常,和/或OOM Killer将运行,试图通过杀死使用大量内存的进程来释放内存。不幸的是,这可能与其他程序一样容易杀死发出请求的程序(事实上,许多试图通过重复分配大块内存来导致分配失败的示例应该是最先被杀死的)。

Windows 的工作方式更接近于 C 和 C++ 标准的设想方式(但只有一点点)Windows 通常配置为在必要时扩展交换文件以满足内存分配请求。这意味着,当您分配更多内存时,系统会因为交换内存而变得半疯狂,创建越来越大的交换文件以满足您的要求。

这最终会失败,但在具有大量驱动器空间的系统上,它可能会运行数小时(其中大部分疯狂地在磁盘上随机移动数据),然后才会发生这种情况。至少在用户实际使用的典型客户端计算机上……嗯,使用计算机时,他会注意到一切都已停止,并在分配失败之前采取措施阻止它。

因此,要获得真正失败的内存分配,您通常会寻找不同于典型台式机的东西。一些示例包括一个服务器在无人值守的情况下一次运行数周,并且负载非常轻,以至于没有人注意到它连续 12 小时在磁盘上颠簸,或者一台运行 MS-DOS 或某些 RTOS 的机器没有提供虚拟内存。

底线:你基本上是对的,他们基本上是错的。虽然如果您分配的内存比机器支持的多,这当然是正确的,但通常不会以 C++ 标准规定的方式发生故障——事实上,对于典型的桌面机器这更像是例外(请原谅双关语)而不是规则。

于 2013-09-08T15:03:50.313 回答
6

除了明显的“内存不足”外,内存碎片也可能导致这种情况。想象一个执行以下操作的程序:

  • 直到主内存快满了:
    • 分配 1020 字节
    • 分配 4 个字节
  • 释放所有 1020 字节块

如果内存管理器将所有这些按分配顺序依次放入内存中,我们现在有大量空闲内存,但任何大于 1020 字节的分配将无法找到连续的空间来放置它们,并且失败。

于 2013-09-08T14:43:26.103 回答
5

通常在现代机器上,由于虚拟地址空间不足,它会失败;如果您有一个 32 位进程试图分配超过 2/3 GB 的内存1,即使有物理 RAM(或页面文件)来满足分配,虚拟地址空间中也不会有空间映射这种新分配的内存。

另一种(类似的)情况发生在虚拟地址空间严重碎片化时,因此分配失败,因为没有足够的连续地址。

此外,可能会出现内存不足的情况,事实上我上周遇到了这种情况;但是在这种情况下,一些操作系统(尤其是 Linux)不会返回 NULL:Linux 会很高兴地为您提供一个指向尚未提交的内存区域的指针,并在程序尝试写入时实际分配它;如果此时没有足够的内存,内核将尝试杀死一些占用内存的进程以释放内存(这种行为的一个例外似乎是当您尝试分配超过 RAM 和交换分区的全部容量时- 在这种情况下,您会得到NULL预付款)。

从 malloc 获取 NULL 的另一个原因可能是操作系统对进程实施的限制。例如,尝试运行此代码

#include <cstdlib>
#include <iostream>
#include <limits>

void mallocbsearch(std::size_t lower, std::size_t upper)
{
    std::cout<<"["<<lower<<", "<<upper<<"]\n";
    if(upper-lower<=1)
    {
        std::cout<<"Found! "<<lower<<"\n";
        return;
    }
    std::size_t mid=lower+(upper-lower)/2;
    void *ptr=std::malloc(mid);
    if(ptr)
    {
        free(ptr);
        mallocbsearch(mid, upper);
    }
    else
        mallocbsearch(lower, mid);
}

int main()
{
    mallocbsearch(0, std::numeric_limits<std::size_t>::max());
    return 0;
}

在 Ideone 上,您发现最大分配大小约为 530 MB,这可能是强制执行的限制setrlimit(Windows 上存在类似机制)。


  1. 它因操作系统而异,通常可以配置;32 位进程的总虚拟地址空间为 4 GB,但在当前所有主流操作系统上,其中很大一部分(默认设置的 32 位 Windows 的上层 2 GB)是为内核数据保留的。
于 2013-09-08T14:43:19.603 回答
1

给定进程可用的内存量是有限的。如果进程耗尽其内存并尝试分配更多内存,则分配将失败。

分配失败还有其他原因。例如,堆可能会变得碎片化,并且没有一个足够大的空闲块来满足分配请求。

于 2013-09-08T14:42:18.577 回答