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