堆中每个已分配内存块都由以下内容组成:在由分配函数返回的地址处开始的数据区以及与数据区相临的控制区(当取消分配内存时,内存管理函数需要控制区以正确释放内存)。如果覆盖堆中控制结构(例如,通过在数组的已分配范围外部对元素进行写入操作,或通过将字符串复制到过小的已分配内存块),那么控制信息会被损坏,并且可能导致错误的程序行为,即使其他已分配块的数据区都未覆盖。
当尝试查找堆错误时,请考虑下列几点:
要检测堆错误,可以编译程序以使用内存管理函数的堆检查版本(有关 -qheapdebug 编译器选项的信息,请参阅相关的调试编译器选项主题)。当运行使用此选项编译的程序时,对内存管理函数所作的每一调用都会对缺省堆执行堆检查。此堆检查涉及检查堆内每个已分配内存块的控制结构,以及确保未覆盖任何控制结构。如果遇到错误,那么程序会终止,并且信息被写至标准错误,其中包括发生堆毁坏的位置的地址、最近一次检测到有效堆状态的源文件和行号以及检测到内存错误的源文件和行号。
堆检查仅针对由每个可执行文件使用的缺省堆启用。如果内存管理函数的调试版本没有报告堆毁坏情况,并且您仍然怀疑存在问题,那么您可能正在使用其他堆并且正在对它们进行毁坏。
可通过以下方式从调试器内准确指出导致堆错误的原因:假定导致错误的堆已知是缺省堆,连续缩小堆有效的最后一行与发生毁坏情况的第一行之间的间隔。使用运行命令、单步执行命令、行和函数断点的组合及停止时执行堆检查设置来缩小搜索范围。有关此设置的信息,请参阅相关主题。
对于语义上不正确的程序,在停止时执行堆检查会进行干预,因为它可导致当程序不正确地访问堆栈上的数据时出现不同结果。这是因为在停止时执行堆检查会使要调试的进程和线程在每次执行停止时都调用堆检查函数,并且此堆检查函数会通过使用其堆栈帧覆盖堆安全区的一部分来影响堆安全区。例如,如果被调用函数返回局部变量的地址,那么该局部变量的内容可从调用函数访问并且不会更改,只要被调用函数使用的堆栈帧未由后续调用覆盖。但是,如果在启用了停止时执行堆检查期间从被调用函数发出“单步返回”,那么在从被调用函数返回时会立即调用堆检查函数,并且由所返回指针指向的内存可能已由堆检查函数的堆栈帧覆盖。
对于单步执行命令,在调试器内进行堆检查的开销很大,因为会在每一步骤之后都检查堆。如果正在单步通过代码的较大部分,或频繁地停止在断点处,并且发现调试性能过低,那么尝试仅在怀疑有问题的区域内打开停止时执行堆检查会导致堆错误。