问题描述
当程序运行时出现如下提示:
说明堆内存被破坏。
原因
写入操作超过了所申请的内存,造成了溢出写入。一个简单的例子说明上述情况:
1 char* ptr = new char[5]; //只申请了5个字节的内存
2 strcpy(ptr, "hello"); //向内存中写入6个字节
3 delete[] ptr;
该bug提示仅在Debug模式下被触发,当执行strcpy函数溢出写入时,并不会出现任何错误提示,程序正常执行。当调用delete[] 释放申请的内存时,才会触发上述提示。
下面分析一下ptr的内存分布:
ptr的内存地址为0x00BFE460,我们申请了5个字节的内存,被编译器默认初始化为cd。但实际编译器为我们申请了9个字节,余下紧跟的四个字节被初始化为fd,这是编译器做的特殊处理。当发生写入溢出了,溢出部分写入到这四字节空间中。后续调用delete[]时,检查后面4个字节的内容是否发生改变,如果值不再是fd,则发生了栈溢出,程序触发堆内存被破坏提示。
我们发现,只有在调用delete时,才会触发该提示,真正导致溢出的代码可能在其他地方。对于复杂的系统,想要定位到具体的代码行可能会很复杂,下面提供几种方法帮助我们迅速定位溢出代码。
排除方案
方法一:监听内存改变
VS提供了名叫 ”数据断点“断点调试功能,在VS2019中,可以通过”调试“选项卡-”新建断点“-”数据断点“ 打开创建数据断点对话框,如下图所示:
在数据断点对话框中,输入地址 ptr + 5,表示当该地址中的值发生改变时,触发该断点。如下图所示:
点击确定,然后继续执行代码,会发现最后代码停留在strcpy函数处。
方法二:修改注册表实现Release版本快速定位
该方法的原理是:将申请到的内存后面紧挨着的几个字节设置为只读状态,如果后续发生写溢出操作,则会对这段只读内存进行写操作,从而触发错误提示。要使用该方法,需要进行如下几步操作:
- 将程序修改为Release编译环境
- 禁用优化,如下图:
- 打开注册表,在下列路径上增加一个项,并设置图中字符串值:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\
打开Release目录:
双击运行.exe文件,会打开VS工程,直接点击运行,程序会直接崩溃在strcpy函数处。需要注意的是,如果没有加载符号文件,手动加载一下。
错误提示是:写入访问冲突。如前所说,对一段只读内存进行写入。
后续将问题代码处理后,可以将注册表中的表项删除或者重命名,将优化选项开启即可。