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

时间:2013-09-08 14:39:15

标签: c++ memory memory-management

这是我在学生时问自己的一个问题,但未能得到令人满意的答案,我一点一点地想到了......直到今天。

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

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

4 个答案:

答案 0 :(得分:16)

虽然你已经得到了很多关于内存 失败的原因/方式的答案,但大多数都忽略了现实。

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

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

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

这最终会失败,但是在具有大量驱动器空间的系统上,它可能会运行小时(大部分都是在磁盘上乱丢乱的数据)。至少在一个典型的客户端机器上,用户实际上......好吧,使用计算机,他会注意到一切都拖延到停止状态,并在分配失败之前做了一些事情来阻止它。

因此,要获得真正失败的内存分配,您通常会寻找其他而不是典型的桌面计算机。一些例子包括一次无人值守运行几周的服务器,并且负载太轻以至于没有人注意到它正在颠倒磁盘12小时,或运行MS-DOS的机器或没有运行MS-DOS的RTOS提供虚拟内存。

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

答案 1 :(得分:5)

除了显而易见的“内存不足”之外,内存碎片也会导致这种情况。想象一下执行以下操作的程序:

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

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

答案 2 :(得分:2)

通常在现代机器上,由于虚拟地址空间稀缺而失败;如果你有一个32位进程试图分配超过2/3 GB的内存 1 ,即使有物理RAM(或分页文件)来满足分配,只需赢得&#39 ; t是虚拟地址空间中的空间,用于映射这种新分配的内存。

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

此外,内存耗尽可能发生,事实上我上周遇到了这样的情况;但是在这种情况下,几个操作系统(特别是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;
}

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


  1. 它在操作系统之间有所不同,通常可以配置; 32位进程的总虚拟地址空间为4 GB,但在所有当前主流操作系统中,其中很大一部分(32位Windows默认设置的高2 GB)是为内核数据保留的。

答案 3 :(得分:0)

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

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