std::map 导致内存不足情况下出现"stack overflow"

std::map causes "stack overflow" under low memory situation

本文关键字:overflow stack 内存不足 map std 情况下      更新时间:2023-10-16

此应用程序是在Windows XP的VS2010中使用c++开发的。

当计算机运行在非常低的物理内存(分页文件被禁用,因为它是我们的测试用例),这行代码:

std::map<UINT, std::vector<void *>> MyMap;

导致malloc.c中的"stack overflow"错误

'return HeapAlloc(_crtheap, 0, size ? size : 1);'

MyApp.exe中0x7c90e8e5的未处理异常:0xC00000FD: Stack overflow.

这个调用是从应用程序的一个线程发出的。如果内存不足是错误,应该抛出bad_alloc

谁能告诉我这是什么原因?

编辑:

这是实际栈的样子

ntdll.dll!7c90e8e5()    
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll] 
ntdll.dll!7c9100d3()    
MyApp.exe!_heap_alloc_base(unsigned int size=72)  Line 55   C
MyApp.exe!_heap_alloc_dbg_impl(unsigned int nSize=36, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0, int * errno_tmp=0x0af3f0e4)  Line 431 + 0x9 bytes   C++
MyApp.exe!_nh_malloc_dbg_impl(unsigned int nSize=36, int nhFlag=0, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0, int * errno_tmp=0x0af3f0e4)  Line 239 + 0x19 bytes C++
MyApp.exe!_nh_malloc_dbg(unsigned int nSize=36, int nhFlag=0, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0)  Line 302 + 0x1d bytes  C++
MyApp.exe!malloc(unsigned int nSize=36)  Line 56 + 0x15 bytes   C++
MyApp.exe!operator new(unsigned int size=36)  Line 59 + 0x9 bytes   C++
MyApp.exe!std::_Allocate<std::_Tree_nod<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >::_Node>(unsigned int _Count=1, std::_Tree_nod<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >::_Node * __formal=0x00000000)  Line 36 + 0x15 bytes C++
MyApp.exe!std::allocator<std::_Tree_nod<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >::_Node>::allocate(unsigned int _Count=1)  Line 187 + 0xb bytes C++
MyApp.exe!std::_Tree_val<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >::_Tree_val<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >(const std::less<unsigned int> & _Parg=less, std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > > _Al={...})  Line 544 + 0xd bytes C++
MyApp.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >::_Tree<std::_Tmap_traits<unsigned int,std::vector<void *,std::allocator<void *> >,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > >,0> >(const std::less<unsigned int> & _Parg=less, const std::allocator<std::pair<unsigned int const ,std::vector<void *,std::allocator<void *> > > > & _Al={...})  Line 699 C++
  #define STACKSIZE (1024*896)

这当然是一个问题,程序的主线程在触发触发堆栈溢出异常的页面保护异常的边缘摇摇欲坠。在Windows上,HeapAlloc()会因为两个基本原因而失败。到目前为止,您一直假设的常见故障是地址空间耗尽,这是正常的故障模式。

但是Windows也提供了一个强有力的保证,任何虚拟内存分配都可以总是映射到RAM。它没有任何类似于"内存不足杀手"的东西,当内存过度使用并且没有可用的RAM来修复页面错误时,它会随机终止进程。分配总是在保证RAM页可以被丢弃或交换到磁盘的情况下提交。如果您运行的Windows没有分页文件,那么这种保证确实很难提供。提交失败是一个明显的可能性。

接下来发生的事情很难猜测,你的堆栈跟踪没有给出任何提示。一定要配置符号服务器,以便获得ntdll.dll的符号。但是很明显,当提交失败时,HeapAlloc()将遵循与通常不同的路径。我猜是某种调试器探针,真的不知道。使用更深的嵌套,需要更多的堆栈空间。这足以触发堆栈保护页并触发stackoverflow异常。

不确定修复这个问题是否真的重要,无论如何你的程序已经死了。从提交失败中恢复几乎是不可能的。它完全是随机的,受其他进程中虚拟机分配的影响。在没有页面文件的情况下运行需要大量的RAM,并且非常小心地控制允许在机器上运行的进程。

内存比你在软件中可能做的任何事情都要便宜得多。

低内存并不意味着总是bad_alloc。调用堆栈也消耗内存。如果系统无法为另一个函数调用创建新的堆栈或达到了调用堆栈数量的限制,则会给出堆栈溢出错误。

我认为HeapAlloc是CRT的一部分(基本上是C函数调用)。new操作符抛出bad_alloc,而不是HeapAlloc

我意识到这并不是std::map所特有的问题。当内存不足且不存在分页文件时,一个简单的应用程序也可能导致堆栈溢出。这是因为Windows为每个线程为堆栈保留了一定数量的内存。但是,它不一定能够提交内存。在这种情况下,"堆栈溢出"发生。

#include <stdio.h>
#include <conio.h>
#define STACKSIZE (1024*896)
void FunctionWithBigStack()
{
    char stack[STACKSIZE];
    printf("nAllocated %d KB stack successfully", STACKSIZE/1024);
    stack[STACKSIZE-1] = 123; // Let's use 'stack' array so that optimizer won't discard it while compiling
}
int _tmain(int argc, _TCHAR* argv[])
{
    printf("nThrough another application, try make all the memory full and then press a key to call a function that has %d KB stack", STACKSIZE/1024);
    _getch();
    FunctionWithBigStack();
    return 0;
}

此应用程序在正常内存条件下运行完美,但如果我们使内存满而等待击键,我们可以看到FunctionWithBigStack崩溃与"堆栈溢出"