【问题标题】:std::map causes "stack overflow" under low memory situationstd::map 在内存不足的情况下导致“堆栈溢出”
【发布时间】:2014-07-29 18:18:15
【问题描述】:

这个应用程序是在 VS2010 上用 C++ 在 Windows XP 上开发的。

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

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

导致 malloc.c 中的“堆栈溢出”错误

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

MyApp.exe 中 0x7c90e8e5 处未处理的异常:0xC00000FD:堆栈溢出。

此调用是从应用程序的线程之一进行的。 如果内存不足是错误,它应该抛出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++

【问题讨论】:

  • 一旦内存不足,堆分配开始失败,这可能会以其他方式表现出来。我猜运行时正在分配失败的堆,并且运行时不会检查失败。然后它假设来自其内部分配的返回值是好的,而实际上不是。到那时,一切都会发生。
  • @DavidHeffernan 这是有道理的,但根据问题中的信息似乎并非如此。有时堆栈溢出实际上只是堆栈溢出。我的第一个想法是堆栈大小并没有那么高,但是内存不足的情况阻止了堆栈区域的扩展。
  • @hvd 这很合理。地址空间被保留,但提交失败。我认为您可以为此添加答案。
  • @DavidHeffernan 谢谢,但我认为主要思想已经在 dbasic 的回答中,尽管措辞有点混乱,所以我投了赞成票。

标签: c++ stl stack-overflow stdmap


【解决方案1】:
  #define STACKSIZE (1024*896)

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

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

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

不太确定解决这个问题是否真的重要,你的程序无论如何都死了。从提交失败中恢复几乎是不可能的。它是完全随机的,受其他进程中 VM 分配的影响。在没有页面文件的情况下运行需要大量 RAM,并且非常仔细控制允许在机器上运行的进程。 RAM 比您在软件中可以做的任何事情都要便宜。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • new 将在系统分配器之上实现。显然HeapAlloc 不会抛出,但由于来自HeapAlloc 的错误返回调用堆栈,它应该被转换为适当的C++ 异常。
    【解决方案3】:

    我意识到这确实不是 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 因“堆栈溢出”而崩溃

    【讨论】:

      猜你喜欢
      • 2012-04-23
      • 1970-01-01
      • 2015-05-21
      • 2014-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多