64位大内存分配

19

在64位系统中,malloc()函数失败的原因有哪些?

我的具体问题是在64位系统上尝试malloc()一个10GB巨大的RAM块。该机器拥有12GB RAM和32GB交换空间。是的,这个malloc()极端了,但为什么会成为问题呢?这是在Windows XP64下使用Intel和MSFT编译器。malloc()有时成功,有时失败,大约50%的概率。8GB的malloc()总是有效的,20GB的malloc()总是失败的。如果一次malloc()失败,重复请求将不起作用,除非我退出进程并再次启动新进程(然后才有50%的成功机会)。没有其他大型应用程序在运行。即使在刚刚重新启动后,它也会发生。

我可以想象,在32位计算机上,如果您使用完可用的32(或31)位地址空间,以至于没有足够大的地址范围可分配给您的请求,那么malloc()会失败。

我也可以想象当你已经使用了你的物理RAM硬盘交换空间时,malloc()会失败。但这对我来说不是问题。

但是,还有什么其他原因导致malloc()失败吗?我想不出其他原因。

我更关心的是一般的malloc()问题,而不是我的具体示例,我可能会用内存映射文件来替换它。失败的malloc()只是一个谜题... 希望理解你的工具并对基础知识不感到惊讶。


1
你是否检查了GetLastError()和errno以获取其他信息? - user82238
@blank:将其作为答案发布,我会给你点赞! - SPWorley
9个回答

8
malloc尝试分配一段连续的内存空间,由于交换内存的工作方式(至少我记得是这样),这最初将在实际内存中。很可能你的操作系统有时无法找到10GB连续内存块并同时保留所有需要实际内存的进程(此时你的malloc将失败)。
你是否真正需要10GB连续内存,或者您能否在几个较小的块周围包装一个存储类/结构,并以块的形式使用内存?这可以放宽对巨大连续内存的要求,并且还应该允许您的程序为不常用的块使用交换文件。

我只比你快了一点点 ;) 将malloc分解成更小的段是正确的,10GB超出了当前主流PC的范围。 - SmacL
2
空白是绝对正确的... 64位地址空间比仅仅10GB要大得多,多得多,多得多!(请注意可能存在隐藏的2^48地址大小限制,但仍然比10GB != 2^33大得多) - SPWorley
1
地址空间本身不会有任何问题。但可用内存不一定会填满地址空间。据我所知,内存无法在虚拟内存中初始分配,因此如果没有办法将物理内存换出以获得10GB连续的块,则malloc操作将失败。即使情况不是这样,12GB和32GB的可寻址内存也无法占满64位的全部空间。 - workmad3
现在正在工作,刚刚在Linux 32位和64位上尝试了一下,我可以成功地分配比物理内存更多的空间。也许这是Windows堆库的限制?快速测试一下,有没有人能在1GB或更少的Windows机器上尝试单个1.1GB的malloc? - SPWorley
1
对于 Linux 系统,在使用大量的 malloc 时,需要注意内存过度提交 [http://linux-mm.org/OverCommitAccounting]。 - Steve Schnepp
显示剩余3条评论

6

您尝试过直接使用VirtualAlloc()VirtualFree()吗?这可能有助于隔离问题。

  • 您将绕过C运行时堆和NT堆。
  • 您可以保留虚拟地址空间,然后提交它。这将告诉您哪个操作失败了。

如果虚拟地址空间预留失败(尽管根据您所说的情况不应该出现这种情况),Sysinternals VMMap可能有助于解释原因。打开“显示空闲区域”以查看空闲虚拟地址空间如何被分段。


3
这里有一份官方资料表明,堆的最大请求大小由您链接的CRT库定义(除了您之前的代码存在整数溢出导致归零,这就是为什么您没有得到NULL返回的原因)(_HEAP_MAXREQ)。

http://msdn.microsoft.com/en-us/library/6ewkz86d.aspx

请看我的答案这里,关于大型窗口分配的问题,我提到了微软有关Vista/2008内存模型改进的论文。

简而言之,即使对于本机64位进程,标准CRT也不支持任何大于4GB的堆大小。您必须使用VirtualAlloc*或CreateFileMapping或其他类似方法。

哦,我还注意到您声称您的更大的分配实际上是成功的,但这是不正确的。您误解了malloc(0x200000000);(在十六进制中为8GB),实际发生的情况是由于测试工具的强制转换或其他影响,您请求了一个0字节的分配,您绝对没有观察到任何大于0xfffff000字节的堆被提交,只是您看到了整数溢出下转换。

建议或*保存堆心理健康的提示*

使用MALLOC(或任何其他动态请求)唯一的方法

void *foo = malloc(SIZE);

动态内存请求的值绝不能(我强调这一点)在请求的“()”括号内计算。
mytype *foo = (mytype *) malloc(sizeof(mytype) * 2);

危险在于可能发生整数溢出。
在调用时进行算术运算始终是编码错误,必须在评估请求的语句之前始终计算要请求的数据的总和。
为什么这么糟糕?因为在请求动态资源的点上,我们知道这是一个错误,必须有一个未来的点在那里我们将使用此资源。
为了使用我们请求的内容,我们必须知道它有多大(例如,数组计数,类型大小等)。
这意味着,如果我们在资源请求的()内看到任何算术运算,那么这是一个错误,因为我们必须再次复制该代码以适当地使用该数据。

2
-1. 有两点错误。(1) 你声称在Windows上使用CRT不能分配超过4GB的内存。这不适用于Win32,但在Win64上可以。在VS2008中进行简单的实验即可确认。我花了5分钟来检查它(编写程序,编译,进入CRT以检查值并观察内部实现)。 (2) 你声称他的大小向下舍入,而实际上并非如此。malloc的输入值(大小)被指定为size_t而不是int。因此,在64位Windows上,您可以指定任何有效的64位大小。8GB或20GB都在该范围内。 - Stephen Kellett
1
Stephen:把你的错误测试用例粘贴上来,我会告诉你哪里出了问题。 - RandomNickName42
一个应该分配10 GB 的计算不会发生整数溢出,除非 size_t 是 32 位 - 在这种情况下,你无法分配 10 GB。在调用本身之外进行的计算是没有帮助的。 - gnasher729
关于始终计算分配大小的观点在分配结构时并不完全正确。mytype* foo = malloc(sizeof(*foo))是完全可以的,传递结构体指针也非常普遍。然而,对于所有类型的缓冲区分配,原始观点是非常正确的。 - markusjm

2

您是否尝试过使用堆函数来分配内存?


2

仅供参考,malloc分配连续内存,而您可能没有足够大的连续空间在堆上。以下是我建议尝试的几件事:

如果20GB的malloc失败了,那么四个5GB的malloc是否成功?如果是这样,那就是连续空间问题。

您是否检查过编译器开关,查看是否有限制总堆大小或最大堆块大小的选项?

您是否尝试编写一个声明所需大小的静态变量的程序?如果这样可以工作,您可以在该空间中实现自己的大型malloc堆。


我不相信 - 有一个64位的虚拟地址空间。我无法理解堆如何会有困难找到10GB连续的内存块。 - user82238
1
可能不是这样的,但如果我们相信原帖的内容,那么事实就是如此。如果您可以将相同数量的内存分配到较小的块中,则很可能操作系统无法提供跨越实际内存和交换空间的大堆块。 - SmacL
1
据我所知,您的虚拟内存不受64位地址空间的限制,而是受主内存和交换文件大小的限制。 - Seun Osewa

1

我觉得这个问题很有趣,所以我试着从理论的角度进行了研究:

在64位(实际上由于芯片限制只能使用48位,由于操作系统限制可能只有44位)中,你肯定不应该受到虚拟内存碎片化的限制,即缺乏连续的虚拟地址空间。原因是虚拟地址空间非常大,用尽它是相当不切实际的。

此外,我们可以预期,物理内存碎片化也不应该成为问题,因为虚拟内存意味着不需要连续的物理内存地址范围来满足分配请求。相反,它可以通过任何足够大的一组内存页面来满足。

所以你一定遇到了其他问题:例如适用于虚拟内存的其他限制。

Windows上肯定存在的另一个限制是提交限制。更多信息请参见:

https://web.archive.org/web/20150109180451/http://blogs.technet.com/b/markrussinovich/archive/2008/11/17/3155406.aspx

还可能存在其他限制,例如实际实现必须与实际硬件配合工作的怪癖。想象一下,在尝试创建虚拟地址空间到物理地址空间的映射时,您用完了页表中的条目来执行虚拟地址映射...操作系统内存分配器代码是否关心处理这种不太可能的情况?也许不会...

您可以在此处阅读有关页面表如何实际工作以执行虚拟地址转换的更多信息:

http://en.wikipedia.org/wiki/Memory_management_unit


1
问题在于,当你编译一个64位应用程序时,Visual Studio没有定义WIN64,通常仍然保留WIN32,这对于64位应用程序是错误的。这会导致运行时在定义了_HEAP_MAXREQ时使用32位值,因此所有大的malloc()都会失败。如果你更改项目(在项目属性下,预处理定义)为WIN64,则非常大的malloc()就不会有任何问题了。

0
但是 malloc 失败的其他原因是什么?我想不出其他原因。
正如之前多次暗示的那样,由于内存碎片化。

0

很可能是内存碎片化。为了简单起见,我们使用一个例子。

内存由一个12kb的模块组成。这个内存在MMU中被组织成1kb的块。所以,你有12个1kb的块。你的操作系统使用100字节,但这基本上是管理页面表的代码。因此,你不能将其交换出去。然后,你的应用程序都使用100字节。

现在,只有你的操作系统和应用程序在运行(200字节),你已经使用了200字节的内存(占用2kb的块)。留下恰好10kb可用于malloc()

现在,你开始通过malloc()分配一些缓冲区——A(900字节)、B(200字节)。然后,你释放了A。现在,你有9.8kb的空闲空间(不连续)。所以,你尝试malloc() C(9kb)。突然间,你失败了。

你在尾端有8.9k的连续空间和0.9k的前端空间。你不能重新映射第一个块到末尾,因为B跨越了第一个1k块和第二个1k块。

你仍然可以malloc()一个8kb的块。

虽然这个例子有点牵强,但希望它能有所帮助。


这不是由于48位地址空间的碎片化造成的。10GB < 2 ** 34字节。 - Daniel

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