当有可用内存时,realloc()返回NULL

4
我有一个运行在Windows 7机器上的C++程序,内存大小为12GB。 编译器和链接器使用Visual Studio 2013 Express。
该程序使用OGDF库。 我使用Release X64配置将库源代码编译成静态库,并在我的项目中引用该库。
当我运行该问题(Debug x64配置)时,从OGDF库中的代码抛出异常,表明没有足够的可用内存;
    E *p = static_cast<E *>( realloc(m_pStart, sNew*sizeof(E)) );
    if(p == 0) OGDF_THROW(InsufficientMemoryException);

我暂停了程序并打开了调试窗口,检查了 sNew = 9M 和 sizeof(E) = 8 的值,因此它正在分配 72M 的内存并失败了。
在调试时,我打开了 Windows 任务管理器,它显示我的程序的内存使用情况(工作集大小和已提交大小)小于 2MB。
因此,我非常困惑为什么 REALLOC 会失败,因为有足够的内存可用(>4GB)?即使我的堆中有很多碎片,已提交的大小也小于 2MB,所以应该有足够的内存。
为了测试目的,在调用库函数之前,我插入了以下代码:
void* ddd = malloc(1200000000);
char* b = (char*)ddd;
char ttt = 3;
int g = 0;
for (g = 0; g < 1200000000;++g)
{
    *(b + g) = ttt;
}
ddd=realloc(ddd, 2400000000);
b = (char*)ddd;
for (g = 0; g < 2400000000; ++g)
{
    *(b + g) = ttt;
}

上面的代码在Debug x64配置下能够正常运行,任务管理器显示我的内存使用量(工作集和分配大小)约为2.3GB,在调用free()函数之前。因此,在我的程序中,我可以在堆上分配超过2GB的内存。但是,为什么库代码中72MB的分配失败了呢?
编辑:
我找到了问题所在。
当我使用release-configuration编译的库文件时,调试器显示了错误的本地变量数据。实际原因是该库调用了realloc(ptr,0)。

1
你的分页文件有多大? - David Schwartz
2
你可能混合了堆。也就是说,如果您的缓冲区是由调试堆分配的,并且库是针对发布进行编译的,那么每个模块将使用不同的堆。然后,该库可能无法重新分配在其未管理的堆上分配的缓冲区。 - Eran
@David Schwartz 分页文件大小是Windows默认大小,我不记得数字了,也许明天可以查一下。但由于在调用库函数之前我可以成功使用和释放2.4GB内存,那么我能否说操作系统有足够的“内存”(RAM+页面文件和VA空间)来提交? - user2218067
@user2218067 不,你不能这么说。你有可用的RAM,但你正在尝试分配支持内存。一个系统可以有很多可用的RAM,但由于之前的预留而没有可用的支持内存。请看我的回答。(但现在似乎你真正的问题是eran所说的。) - David Schwartz
@user2218067,MS CRT(C运行时)有两种堆 - 调试和发布,分别用于匹配构建配置。在使用调试配置构建可执行文件时,您必须确保所有模块(静态库和动态库)都是使用相同的配置构建的。查看OGDF库的构建方式,并确保您的代码使用相同的配置(最重要的是_DEBUG定义)。 - Eran
显示剩余4条评论
1个回答

2
你的问题与“你有2000美元在银行里,为什么不给我写一张500美元的支票?”非常相似。
操作系统会在使用内存之前就收到申请请求。除非操作系统已经为所有已允许的请求提供了足够的后备存储空间(无论它们当前是否正在使用任何RAM),否则操作系统无法授予内存分配请求。
例如,如果你调用malloc函数分配1GB的内存,但尚未访问已分配的虚拟地址空间,那么分配的内存将使用少于1GB的RAM。但在释放该分配之前,系统必须保留1GB的后备存储空间(RAM或交换空间),以防你的程序开始使用该分配的空间。
如果系统有太多这样的分配,即使它有大量可用的RAM,也会拒绝新的分配。Windows不会过度提交,因为这会冒着强制终止应用程序的风险,如果这些映射后来需要更多的后备存储空间而操作系统没有足够的空间。
避免今后出现这些误解的好方法是避免单独使用单词“内存”。如果你指的是RAM,请说“RAM”或“物理内存”。如果你指的是后备存储空间,请说“后备存储空间”。如果你指的是地址空间,请说“虚拟内存”。
你会说:“我有很多可用的内存,所以我应该能够分配内存。”这听起来像个谜。但如果你更准确地说,“我有很多可用的RAM,为什么不能分配更多的虚拟内存(或支持内存)?”,那么你已经解决了一半的问题。

我的Win8系统显示页面文件的最小大小为16MB。如果你说的是真的,那么这个合法设置是不允许系统加载的。考虑到他提供的数据(64位,12GB RAM,分配72MB),我怀疑页面文件不是问题的原因。 - Eran
@eran 嗯?我完全无法理解你的评论。它看起来就像是一堆杂乱无章的词语。(72MB的分配失败只意味着操作系统剩余不到72MB的未保留后备存储空间。) - David Schwartz
我假设你所说的“备份存储”是指页面文件。页面文件可以非常小,不管RAM的大小(虽然不建议这样做,但是合法的)。如果每个分配的字节都必须在页面文件中有相应的存储空间,将页面文件设置为16MB将只允许分配16MB的内存。祝用餐愉快。 - Eran
备份存储是指在谈论 RAM 时的页面文件。但我所说的是虚拟内存的分配,而不是 RAM。这些可以由 RAM 或页面文件支持,只要该空间未被先前分配保留(无论该分配是否正在使用它)。 - David Schwartz
他正在编译为64位,因此虚拟地址空间肯定不会短缺(除非请求的分配大小与所述的不同)。如果您指的是总内存消耗接近页面文件+RAM大小的情况-OP说他有> 4GB的可用内存;如果这是真的,他应该远离备份存储限制。 - Eran
我不是在谈论虚拟地址空间。是的,他有足够的虚拟地址空间和充足的空闲RAM。他所没有的是未保留的后备存储(RAM或交换空间)来支持分配(因为它已被先前的分配保留,但尚未使用RAM)。当您调用realloc来增加分配时,如果没有足够的后备存储来支持该分配和操作系统允许的每个先前的分配(无论这些分配使用了多少RAM),则该分配将失败。请再次阅读我的答案。 - David Schwartz

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