提问人:dyp 提问时间:8/21/2010 最后编辑:dyp 更新时间:8/24/2010 访问量:3540
C++/Windows:如何报告内存不足异常(bad_alloc)?
C++/Windows: How to report an out-of-memory exception (bad_alloc)?
问:
我目前正在为Windows MSVC++(9.0)应用程序开发一个基于异常的错误报告系统(即异常结构和类型/继承,调用堆栈,错误报告和日志记录等)。
我现在的问题是:如何正确报告和记录内存不足错误?
当此错误发生时,例如,作为操作抛出的错误,可能有许多“功能”不可用,主要是关于进一步的内存分配。通常,如果异常被抛出到库中,我会将异常传递给应用程序,然后使用消息框和错误日志文件来报告和记录它。另一种方法(主要用于服务)是使用 Windows 事件日志。
我遇到的主要问题是组装错误消息。为了提供一些错误信息,我想定义一个静态错误消息(可能是字符串文字,最好是消息文件中的条目,然后使用 FormatMessage)并包含一些运行时信息,例如调用堆栈。
此用途所需的函数/方法bad_alloc
new
- 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] 消耗大量内存,因此释放预分配的堆空间不是避免想要报告错误的进程内存环境不足的安全解决方案。
先谢谢你!
答:
“释放预先分配的堆空间...”。这正是我读到你的问题时所想的。但我认为你可以尝试一下。每个进程都有自己的虚拟内存空间。对于另一个进程消耗大量内存,如果整个计算机都在工作,这仍然可能起作用。
评论
请注意,可能有多个进程占用大量内存,因此释放预分配的堆空间并不是一个安全的解决方案,以避免想要报告错误的进程的内存环境不足。
在 Windows(和其他现代操作系统)下,每个进程都有自己的地址空间(也称为内存),与其他正在运行的进程分开。所有这些都与机器中的字面 RAM 是分开的。操作系统已将进程地址空间虚拟化,使其远离物理 RAM。
这就是 Windows 能够将进程使用的内存推送到硬盘上的页面文件中的方式,而这些进程不知道发生了什么。
这也是单个进程可以分配比计算机具有的物理 RAM 更多的内存,但仍能运行的方式。例如,在具有 512MB RAM 的机器上运行的程序仍然可以分配 1GB 的内存。Windows 无法同时将所有内容保存在 RAM 中,其中一些将位于页面文件中。但程序不会知道。
因此,如果一个进程分配内存,它不会导致另一个进程的内存减少。每个过程都是独立的。
每个过程只需要担心自己。因此,释放预先分配的内存块的想法实际上非常可行。
评论
如上所述,不能使用 CRT 或 MessageBox 函数来处理 OOM,因为它们可能需要内存。唯一真正安全的事情是在启动时分配一个内存块,您可以将信息写入并打开文件或管道的句柄,然后在 OOM 输出时将 WriteFile 写入它。
评论
- 预先分配您需要的缓冲区
- 静态链接并使用 _beginthreadex 而不是 CreateThread(否则,CRT 函数可能会失败) -- 或者 -- 自己实现字符串 concat / i2a
- 使用 MessageBox (MB_SYSTEMMODAL |MB_OK) MSDN 在报告 OOM 条件时提到了这一点(一些 MS 博主将这种行为描述为预期:消息框不会分配内存。
日志记录更难,至少,日志文件需要已经打开。
可能最好使用 和 ,以避免任何缓冲尝试。第一个要求写入和内存缓冲区对齐(即,您需要查询 GetDiskFreeSpace,按此对齐缓冲区,并且仅写入“扇区大小的倍数”文件偏移量,并且块是扇区大小的倍数。我不确定这是否必要或有帮助,但每次分配都失败的系统范围的 OOM 很难模拟。FILE_FLAG_NO_BUFFERING
FILE_FLAG_WRITE_THROUGH
评论