C++/Windows:如何报告内存不足异常(bad_alloc)?

Posted

技术标签:

【中文标题】C++/Windows:如何报告内存不足异常(bad_alloc)?【英文标题】:C++/Windows: How to report an out-of-memory exception (bad_alloc)? 【发布时间】:2011-04-01 19:08:16 【问题描述】:

我目前正在为 Windows MSVC++ (9.0) 应用程序开发基于异常的错误报告系统(即异常结构和类型/继承、调用堆栈、错误报告和日志记录等)。 我现在的问题是:如何正确报告和记录内存不足错误? 发生此错误时,例如作为new 操作抛出的bad_alloc,可能有许多“功能”不可用,主要涉及进一步的内存分配。通常,如果异常已被抛出到一个库中,我会将其传递给应用程序,然后使用消息框和错误日志文件来报告和记录它。另一种方法(主要用于服务)是使用 Windows 事件日志。我遇到的主要问题是组合错误消息。 为了提供一些错误信息,我想定义一个静态错误消息(可能是字符串文字,最好是消息文件中的条目,然后使用 FormatMessage)并包含一些运行时信息,例如调用堆栈。 此用途所需的功能/方法要么

STL (std::string, std::stringstream, std::ofstream) CRT (swprintf_s, fwrite) 或 Win32 API (StackWalk64, MessageBox, FormatMessage, ReportEvent, WriteFile)

除了在 MSDN 上记录之外,它们在 Windows 中都更多(Win32)或更少(STL)封闭源代码,所以我真的不知道它们在低内存问题下的行为。 为了证明可能有问题,我写了一个微不足道的小应用程序引发了 bad_alloc:

int main()

    InitErrorReporter();  

    try
    
        for(int i = 0; i < 0xFFFFFFFF; i++)
        
            for(int j = 0; j < 0xFFFFFFFF; j++)
            
                char* p = new char;
            
        
    catch(bad_alloc& e_b)
    
        ReportError(e_b);
    

    DeinitErrorReporter();

    return 0;

运行了两个没有附加调试器的实例(在版本配置中,VS 2008),但“没有发生任何事情”,即我在错误报告中内部使用的 ReportEvent 或 WriteFile 中没有错误代码。然后,启动一个带调试器和一个不带调试器的实例,并让它们尝试通过在 ReportError 行上使用断点一个接一个地报告错误。对于附加了调试器的实例,这工作得很好(正确报告并记录了错误,即使使用 LocalAlloc 没有问题)!但是 taskman 表现出一种奇怪的行为,在应用程序退出之前释放了大量内存,我想是在抛出异常时。

请考虑可能有多个进程 [edit] 和多个线程 [/edit] 消耗大量内存,因此释放预分配的堆空间不是一个安全的解决方案,以避免要报告的进程的内存不足环境错误。

提前谢谢你!

【问题讨论】:

您是否考虑过提前保留一块内存,当系统进入内存不足的情况时,它将成为放置分配的来源?我只是为了优雅地退出应用程序而使用这种方法,但操作系统(OpenSolaris、Linux)会做类似的事情来给应用程序足够的时间来释放或换出低优先级分配并优雅地恢复。跨度> 目前,我使用一些堆栈空间(调用 InitErrorReporter 时声明的成员变量)为 CRT / WinSDK 函数提供缓冲区。但我不知道他们在内部做什么 - 请参阅 Alex Farber 的分析器。 ***.com/questions/1308052/… 谈点类似的事情 What's the graceful way of handling out of memory situations in C/C++?的可能重复 很抱歉这条消息是骗人的——我想把它放在另一个问题上。 【参考方案1】:

“正在释放预先分配的堆空间...”。这正是我在阅读您的问题时所想的。但我觉得你可以试试。每个进程都有自己的虚拟内存空间。由于另一个进程消耗大量内存,如果整个计算机都在工作,这仍然可以工作。

【讨论】:

k,但假设在一个应用程序中有两个线程同时运行不同库的函数。一个进程 -> 一个虚拟内存空间。那么当一个抛出一个 bad_alloc 然后释放预先分配的空间时,另一个可以分配它吗?问题是当我使用OS/SDK函数时,我不知道它们内部是否依赖头部空间,所以使用预先分配的空间来操作我认为没有解决方案。 我必须先挂起所有其他线程,然后才能使用此技术? 另一个线程可能会立即使用刚刚释放的预分配内存......每个活动都应该停止以确保错误报告成功。【参考方案2】: 预分配您需要的缓冲区 静态链接并使用 _beginthreadex 而不是 CreateThread(否则,CRT 函数可能会失败)- 或者 -- 自己实现字符串 concat / i2a 使用 MessageBox (MB_SYSTEMMODAL | MB_OK) MSDN 在报告 OOM 条件时提到了这一点(一些 MS 博主按预期描述了这种行为:消息框不会分配内存。)

日志记录更难,至少,日志文件需要已经打开。

最好使用FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH,以避免任何缓冲尝试。第一个要求写入并且您的内存缓冲区是扇区对齐的(即您需要查询GetDiskFreeSpace,以此对齐您的缓冲区,并且只写入“扇区大小的倍数”文件偏移量,并且写入扇区大小的倍数的块中。我不确定这是否有必要或有帮助,但是很难模拟 每个 分配失败的系统范围的 OOM。

【讨论】:

非常感谢。我做了一些测试(但我不知道它们有多重要)和 MessageBox | MB_SYSTEMMODAL 总是出现,即使在捕获了几个 bad_alloc 之后也是如此。为此,我使用了一些基于我自己答案中的代码,但决定为 x64 / Release 构建它。我使用 x86 收到的 bad_allocs 主要是虚拟地址空间不足错误,因为我有 4 GB 的 RAM 加上(现在)2 GB 的最大页面文件大小。 x64版本,系统真的很严重,有时甚至是光标。但是,MB 仍然有效,而且我提到的页面也可以使用这个技巧!【参考方案3】:

请考虑可能有多个进程消耗大量内存,因此释放预分配的堆空间不是一种安全的解决方案,以避免要报告错误的进程处于低内存环境。

在 Windows(和其他现代操作系统)下,每个进程都有自己的地址空间(也称为内存),与其他所有正在运行的进程分开。所有这些都与机器中的文字 RAM 分开。操作系统已将进程地址空间虚拟化为远离物理 RAM。

这就是 Windows 能够将进程使用的内存推送到硬盘上的页面文件中的方式,而这些进程并不知道发生了什么。

这也是单个进程如何分配比机器拥有的物理 RAM 更多的内存但仍然运行的方式。例如,在具有 512MB RAM 的机器上运行的程序仍然可以分配 1GB 内存。 Windows 无法同时将所有内容保存在 RAM 中,而其中一些内容将保存在页面文件中。但程序不会知道。

因此,如果一个进程分配内存,它不会导致另一个进程使用更少的内存。每个过程都是独立的。

每个进程只需要关心自己。因此,释放预先分配的内存块的想法实际上是非常可行的。

【讨论】:

嗯?这是不正确的。虽然每个进程都有自己的 VA 空间,但有有限数量的物理帧和交换空间来满足页面调入请求。如果总提交费用超过这个数字,每个进程的分配请求都会失败。如果您遇到这种情况,释放一块内存意味着系统中的任何进程都可以使用它(即任何进程都可以再次提高提交费用)。 @Paul:你说得有道理。无论操作系统做什么,最终总会有一个限制。虽然,在操作系统的内存机制已被最大化的情况之外,我想知道程序的行为是否不合理,就好像它们总是有完整的虚拟地址空间可供使用。但我想这可能是针对特定情况的,这取决于最终用户机器将其资源最大化的可能性以及在这种情况下登录实际有用的程度。 是的,有一个限制。这是你硬盘的大小。那是很多记忆。在绝大多数机器上,RAM 分配不到 50%,而可用的 HDD 空间如此之多,现实情况是您的进程只需要担心自己。 我认为主要限制是 32 位应用程序中的虚拟地址空间。请参阅msdn.microsoft.com/en-us/library/aa366778(VS.85).aspx 但这不是主要问题。当您唯一的问题是您的地址空间已满(如何确定?)时,您可以释放一块内存并继续工作。 @DeadMG blogs.technet.com/b/markrussinovich/archive/2008/11/17/… - “最大值是 RAM 大小的三倍或 4GB,以较大者为准”【参考方案4】:

您不能使用 CRT 或 MessageBox 函数来处理 OOM,因为它们可能需要内存,正如您所描述的。唯一真正安全的做法是在启动时分配一块内存,您可以将信息写入其中并打开文件或管道的句柄,然后在 OOM 退出时将 WriteFile 写入它。

【讨论】:

所以你的意思是,不是预先分配然后释放一个块,而是预先分配一个区域,专门用于处理 OOM 场景中记录的数据(一种排序用于组装字符串或任何所需的暂存缓冲区)。假设 WriteFile 在 OOM 场景中没有失败的原因,那么我不得不同意这听起来是记录信息的最佳保证方式。 是的,如果操作系统必须保证某些东西在内存不足的情况下工作(比如能够引发 SEH 异常,ntdll 总是在它的后袋中保留一个预分配的异常),这就是操作系统所做的) 你知道创建不同种类的 Win32 对象在 OOM 条件下的行为吗? IE。诸如文件(-> 文件句柄)之类的内核对象和诸如 MessageBoxes 之类的 GUI 对象?据我所知,至少内核对象不属于您的虚拟内存/地址空间,因此操作系统实际上会为它们分配内存。 @DyP:你错了。操作系统确实在您的地址空间内分配。不过,这两者分开的方式,应该没问题。这就是为什么在 32 位机器上你会得到 2GB 的限制,而不是 4GB——因为操作系统会保留另外 2GB 的空间来满足它的需要。但是,如果您分配失败,那是因为您的一半已满 - 不一定是整个空间。无法保证 OS 2GB 处于或不处于任何状态,包括是否已满。 @DyP 内核对象也需要内存,并且它们也受到系统范围的 OOM 的影响 - 如果分配失败,它将最终使用 GetLastError 传播回来

以上是关于C++/Windows:如何报告内存不足异常(bad_alloc)?的主要内容,如果未能解决你的问题,请参考以下文章

保留内存是不是会导致内存不足异常

我的 C++ 程序在内存不足时究竟是如何终止的?

如何检测大对象堆是不是导致内存不足异常

[error]:启用sqlserver配置管理器异常,内存不足

云帮手在windows下提示虚拟内存不足,如何解决?

大位图内存不足异常