8

我目前正在为 Windows MSVC++ (9.0) 应用程序(即异常结构和类型/继承、调用堆栈、错误报告和日志记录等)开发基于异常的错误报告系统。

我现在的问题是:如何正确报告和记录内存不足错误?

当这个错误发生时,例如bad_alloc由操作抛出new,可能有许多“功能”不可用,主要涉及进一步的内存分配。通常,如果异常已被抛出到一个库中,我会将其传递给应用程序,然后使用消息框和错误日志文件来报告和记录它。另一种方法(主要用于服务)是使用 Windows 事件日志。
我遇到的主要问题是组装一条错误消息。 为了提供一些错误信息,我想定义一个静态错误消息(可能是字符串文字,最好是消息文件中的条目,然后使用 FormatMessage)并包含一些运行时信息,例如调用堆栈。
此用途所需的功能/方法

  • STL ( std::string, std::stringstream, std::ofstream)
  • 阴极射线管 ( 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] 消耗大量内存,因此释放预分配的堆空间不是一个安全的解决方案,以避免要报告的进程的内存不足环境错误。

先感谢您!

4

4 回答 4

3

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

于 2010-08-20T17:53:41.307 回答
2
  • 预先分配您需要的缓冲区
  • 静态链接并使用 _beginthreadex 而不是 CreateThread(否则,CRT 函数可能会失败)——或者——自己实现字符串 concat / i2a
  • 使用 MessageBox (MB_SYSTEMMODAL | MB_OK) MSDN 在报告 OOM 条件时提到了这一点(一些 MS 博主按预期描述了这种行为:消息框不会分配内存。)

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

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

于 2010-08-23T19:24:37.273 回答
1

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

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

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

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

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

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

于 2010-08-20T18:26:10.737 回答
0

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

于 2010-08-20T23:25:49.040 回答