分配内存如何以及为何会失败

How and why an allocation memory can fail?

本文关键字:何会 失败 内存 分配      更新时间:2023-10-16

这是我学生时代问过自己的一个问题,但没有得到一个令人满意的答案,我一点一点地把它弄明白了......直到今天。

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

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

虽然你已经得到了许多关于记忆为什么/如何失败的答案,但其中大多数都忽略了现实。

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

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

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

这最终会失败,但在具有大量驱动器空间的系统上,它可能会运行数小时(其中大部分在磁盘上疯狂地打乱数据)才会发生这种情况。至少在用户实际...好吧,使用计算机,他会注意到一切都已经停止了,并在分配失败之前做一些事情来阻止它。

因此,要获得真正失败的内存分配,您通常正在寻找典型台式机以外的其他东西。一些例子包括一台服务器一次无人值守运行数周,负载非常轻,以至于没有人注意到它连续 12 小时捶打磁盘,或者运行 MS-DOS 或某些不提供虚拟内存的 RTOS 的计算机。

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

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

  • 直到主内存几乎满了:
    • 分配 1020 字节
    • 分配 4 个字节
  • 释放所有 1020 字节块
如果内存管理器按分配

顺序将所有这些按顺序放入内存中,我们现在有足够的可用内存,但任何大于 1020 字节的分配都将无法找到一个连续的空间来放置它们,并失败。

通常在现代机器上,由于虚拟地址空间的稀缺,它会失败;如果你有一个 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)是为内核数据保留的。

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

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