Answers:
与Windows调试工具结合使用的应用程序验证程序是一个了不起的设置。您既可以将其作为Windows驱动程序工具包的一部分,也可以作为更轻巧的Windows SDK的一部分。(在研究有关堆损坏问题的较早问题时,发现了有关Application Verifier的信息。)我过去也曾使用BoundsChecker和Insure ++(在其他答案中提到),尽管我很惊讶Application Verifier中有多少功能。
值得一提的是Electric Fence(又称“ efence”),dmalloc,valgrind等,但是与Windows相比,它们大多数都可以在* nix下运行。Valgrind非常灵活:我已经调试了大型服务器软件,并且使用它时遇到很多堆问题。
当所有其他方法都失败时,您可以为自己的全局运算符提供new / delete和malloc / calloc / realloc重载-具体操作取决于编译器和平台,这样做会有所不同-这将是一笔投资-但从长远来看可能会有所回报。理想的功能列表应该从dmalloc和electricfence以及令人惊讶的出色著作《Writing Solid Code》中看起来很熟悉:
请注意,在我们的本地自制系统(针对嵌入式目标)中,我们将跟踪与大多数其他内容分开,因为运行时开销要高得多。
如果您对重载这些分配函数/运算符的更多原因感兴趣,请查看我的回答“是否有任何理由使全局运算符new和delete重载?” ; 除了无耻的自我推广,它还列出了有助于跟踪堆损坏错误的其他技术以及其他适用的工具。
因为我在搜索MS使用的alloc / free / fence值时一直在这里找到自己的答案,所以这是另一个涵盖Microsoft dbgheap填充值的答案。
通过为应用程序启用Page Heap,可以检测到很多堆损坏问题。为此,您需要使用Windows调试工具中附带的gflags.exe 。
运行Gflags.exe,然后在可执行文件的“图像文件”选项中,选中“启用页面堆”选项。
现在重新启动您的exe并附加到调试器。启用页面堆后,只要发生堆破坏,应用程序就会进入调试器。
一篇非常相关的文章是使用Application Verifier和Debugdiag调试堆损坏。
什么样的事情会导致这些错误?
用内存做一些顽皮的事情,例如在缓冲区结束后写入,或在将其释放回堆之后再写入缓冲区。
如何调试它们?
使用一种在可执行文件上添加自动边界检查的工具:即Unix上的valgrind或Windows上的诸如BoundsChecker之类的工具(维基百科也建议使用Purify和Insure ++)。
请注意,这些操作会减慢您的应用程序的速度,因此,如果您使用的是软实时应用程序,则它们可能无法使用。
另一种可能的调试辅助工具/工具可能是MicroQuill的HeapAgent。
我从检测对释放的内存的访问中获得的一个快速提示是:
如果要快速找到错误,而不检查访问该内存块的每个语句,则可以在释放该块后将内存指针设置为无效值:
#ifdef _DEBUG // detect the access to freed memory #undef free #define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666; #endif
我发现每次都有用且有用的最佳工具是代码审查(与好的代码审查员一起)。
除了代码审查之外,我首先尝试使用Page Heap。Page Heap需要花费几秒钟来设置,如果运气好的话,它可能会指出您的问题。
如果Page Heap没有运气,请从Microsoft 下载Windows调试工具,并学习使用WinDbg。抱歉无法提供更多具体帮助,但是调试多线程堆损坏不是一门科学,而是一门艺术。Google针对“ WinDbg堆损坏”,您应该找到有关该主题的许多文章。
您可能还需要检查是否要针对动态或静态C运行时库进行链接。如果您的DLL文件针对静态C运行时库进行链接,则DLL文件具有单独的堆。
因此,如果您要在一个DLL中创建一个对象,然后尝试在另一个DLL中释放该对象,则会得到与上面看到的相同的消息。另一个堆栈溢出问题(释放另一个DLL中分配的内存)中引用了此问题。
您使用哪种类型的分配功能?最近,我使用Heap *样式分配函数遇到了类似的错误。
原来,我用HEAP_NO_SERIALIZE
选项错误地创建了堆。这实质上使Heap函数在没有线程安全的情况下运行。如果使用得当,可以提高性能,但是如果您在多线程程序中使用HeapAlloc,则永远不要使用它[1]。我之所以只提及这一点,是因为您的帖子中提到您有一个多线程应用程序。如果您在任何地方使用HEAP_NO_SERIALIZE,请将其删除,这很可能会解决您的问题。
[1]在某些情况下,这是合法的,但它要求您序列化对Heap *的调用,而多线程程序通常不是这种情况。
如果这些错误是随机发生的,则很有可能遇到数据争用。请检查:您是否修改了来自不同线程的共享内存指针?英特尔®线程检查器可能有助于检测多线程程序中的此类问题。
您可以对_CrtSetDbgFlag使用VC CRT堆检查宏:_CRTDBG_CHECK_ALWAYS_DF或_CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF。
我想补充一下我的经验。在过去的几天中,我在应用程序中解决了此错误的一个实例。在我的特殊情况下,代码中的错误是:
Control.Invoke
并处理托管对象,该对象包装了回调所属的本机对象。Control.Invoke
结束)。我应该澄清我使用boost::thread
,所以我使用成员函数作为线程函数。Control.BeginInvoke
(我的GUI用Winforms制作),以便本机线程可以在对象被销毁之前结束(回调的目的恰恰是通知线程已结束并且对象可以被销毁)。