HEAP: Free Heap block 0000028A24DF5A10 modified at 0000028A24DF5A50 after it was freed 正确解决方法
现象:
qt程序开发中,该错误,在调试过程会打印,但是直接运行是不会打印出来的(虽然没有打印,但是说明程序已经是带着内存bug运行了,很危险,随时可能崩溃)
首先参考这几个博客:
- 如何解决 " HEAP: Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed "_heapfree报错-CSDN博客
- 有关HEAP: Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed
- https://www.cnblogs.com/lisuyun/articles/6212507.html 重点看这篇博客
- HEAP: Free Heap block 39rt98 modified at 39rtc0 after it was freed_if (!heapfree(select_heap(block), 0, block))-CSDN博客这个也可以看看
- 自己动手解决HEAP: Free Heap block XXX modified at YYY after it was freed问题_heap:free heap block modified-CSDN博客这个博客解决方法新颖,重载了new delete,且自己实现个小工具查找错误位置。但是可能稍微麻烦些了。
这个问题原因:
new了一块内存给p,使用后,delete p了,然后又给p指向的内存赋值(即写内存)等操作(不会报错),这本身就是非法的(说明代码逻辑是有问题的),但是运行和调试阶段并不会报错,直到下次又需要new一块内存,而该内存刚好是上次我们非法使用的内存区,此时才会报上面的错误了。可以看出,报错的位置,其实距离我们非法使用的语句,可能相差十万八千里了,因此非常难以定位。
网上有很多博客,暴力分析自己的代码,从而找到问题语句所在,但是一个上万行的代码,可能就找非常难找到了,这里就要用的有效的调试工具了。
先说结论:用了 gflags.exe 工具开启了heap检查后,给p指向的内存赋值(以及读)等操作,就立刻会报错程序断下来了(不用等到下次new到这块内存)。这样就能第一时间知道操作这个非法内存了,然后查找问题就容易得多。
解决方法:
- 用 gflags.exe 工具,该工具是Windows系统的调试工具,可以先在系统里搜索一下这个exe,如果能找到,那就直接用,找不到的话,就先安装个 WDK(windows中的调试运行工具套件),Download the Windows Driver Kit (WDK) - Windows drivers | Microsoft Learn
- 默认是放在C盘的 C:\Program Files\Windows Kits\10\Debuggers\x64下面的。里面就有 gflags.exe 工具。我应该是安装visual studio 2022时候,默认给安装上这个套件了。
- 然后将 gflags.exe 所在文件夹路径,添加到系统环境变量。
- 打开powershell命令行,进入我们qt程序debug那个目录中去,即xx.exe同级目录,然后命令行中输入 gflags,回车,会出现 gflags的界面:填入我们 xx.exe名字(不需要绝对路径因为命令行已经定位到这儿才打开的gflags.exe嘛,所以gflags.exe会自动知道这个xx.exe全路径的),勾选上 Enable page heap,然后点击Launch按钮,即可。(或者命令行 开启gflags: gflags -p /enable ***.exe /full,此外还可以加它 /unaligned #非对齐方式启用page heap,这个能检测1字节单位的异常都可以(默认是4字节对齐检测的),但是会大量增加内存消耗调试效果变很慢)
(此时,gflags.exe会写注册表一些信息,告诉windows软件启动器以及底层内存管理器,这个xx.exe软件每次释放完一块内存,就将它的堆里面都填满int3指令,下次再有谁非法使用这块内存,直接进入int3断点,当然,这个是有性能损失的,因为内存的申请释放底层都会进行相关多余操作了,我们当然也能无效掉gflags这个设置 ,即命令行:gflags -p /disable ***.exe,或者下面的界面中去掉那个勾选,点击应用,就可以了。注:只有可视化这种去掉勾选的方式,才能再次报错HEAP: Free Heap block xxx...问题(即完全恢复原来的无heap检查运行状态),不然用命令行去掉heap检查方式,虽然去掉了堆检查功能,但是也不再报错了,这样是不好的,因为出错了我们也不知道) - 在qtcreator(采用的是msvc版本qt和编译器编译运行的当前程序)里,再次启动调试,就能在非法使用内存语句那里自动断下来了,牛逼吧~~~
如果不挂着这个gflags.exe运行,那么调试时候,报错上面的 HEAP: Free Heap block xxx...(前提是内存分配刚好冲突得的情况下!!!),但是报在哪呢,在下面位置:所以,着谁能找到最初错误代码的位置呢,所以还是得挂着gflags.exe运行
(还有一点需要注意,用mingw编译器,调试运行,可能仍然报错,但是信息可能不是HEAP: Free Heap block xxx...且无法定位源码位置) msvc就能直接关联上源码定位出来,报错也是HEAP: Free Heap block xxx...。所以windows系统上,各方面来看,msvc编译器和调试器功能还是更为强大和精确的。
如果用直接debug模式(不是启动调试)构建运行方式,两个编译器后程序运行结果都不会报错。
从上面的上面图中,我们还能看到,clang这个代码分析器其实也挺牛逼的,它已经静态分析出这里有非法使用内存了,但是也不是所有的地方都能分析出来的,复杂情况就只能通过上面的调试了。
此外,同样的代码,好像有时发生,重启电脑后,有时候调试又不报错了,但是挂着gflags.exe 工具是一定能中断下来的。
这里再补充一点,之所以调试模式也能报错一些内存相关信息,而直接运行模式不能报错呢,这是因为调试器会将内存赋一些信息,以及跟踪这些内存,发现异常,就报错:(参考博客:C++开发典型内存错误0xCDCDCDCD, 0xDDDDDDDD, 0xFEEEFEEE, 0xCCCCCCCC, 0xABABABAB总结-CSDN博客)
0xCDCDCDCD - Created but not initialised 未初始化的堆内存
0xDDDDDDDD - Deleted 引用的内存已经/对象被删除。当启用gflags.exe的heap检查后,再用命令行的gflags -p /disable treeviewHeapTest.exe命令(此时未彻底关闭gflags对heap的操作功能),调试启动运行,就会填充这玩意,而且读写这里内存也不会报任务错误信息(从最前面的分析和现在实测有:而且后面的new了这块区域,也不会报HEAP: Free Heap block xxx...错误),仅仅供调试追踪内存查看用。
0xFEEEFEEE - Freed memory set by NT's heap manager。彻底关闭gflags.exe的heap检查后(即用上面说的可视化界面关闭),调试启动运行,就会填充这玩意,而且读写这里内存也不会报任务错误信息,也是仅仅供调试追踪内存查看用。
0xCCCCCCCC - Uninitialized locals in VC6 when you compile w/ /GZ 未初始化的栈内存
0xABABABAB- Memory following a block allocated by LocalAlloc()
另一个与这个问题相关的实战示例,可以看这个博客:qtreeview控件水平滚动条自动出现方法与避坑-CSDN博客
测试代码:
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QDebug>#include <stdlib.h>
#include <string.h>void test(void);
void testStruct(void);
void tt(void);struct tagTest
{int a;int b;
};void test(void)
{char* pBuffer = new char[128];strcpy(pBuffer, "hello");printf("%p %s\n", pBuffer, pBuffer);delete[] pBuffer;strcpy(pBuffer, "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");printf("%p %s\n", pBuffer, pBuffer);tt();
}
void tt(void)
{printf("I do nothing\n");
}
void testStruct(void)
{tagTest* pTest = new tagTest();pTest->a = 0x11111111;pTest->b = 0x22222222;// printf("%p %d, %d\n", pTest, pTest->a, pTest->b);qDebug()<<pTest->a<<pTest->b;delete pTest;// int tmp = pTest->a;qDebug()<<"tmp:"<<2;pTest->a = 0x33333333;pTest->b = 0x44444444;printf("%p %d, %d\n", pTest, pTest->a, pTest->b);qDebug()<<pTest->a<<pTest->b;tt();
}MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{//你好呀ui->setupUi(this);qDebug()<<"你好小伙子"; ui->pushButton_3->setText("haha;");testStruct();test();int* p = new int;int* pp = p;delete p;*pp = 11111;p = new int;delete p;}MainWindow::~MainWindow()
{delete ui;
}