一项目,Debug调试完成,编译都OK,没问题,运行,每次运行2小时左右,就崩溃,出现标题上的错误信息,有未处理的报警,第一时间的反应是内存泄露,因为调试编译没问题,而且能运行2小时,说明至少结构没啥大问题,开始查泄露,一步步来,将线程一个个关掉,用_CrtDumpMemoryLeaks()函数一个个函数检查,将几处有可能泄露的GDI对象(如CImage,CFont)全部使用全局变量,由于这些对象之前在函数里面已经释放了,所以即使用全局变量的形式,也仍然没有阻止泄露的产生,仍然是每分钟4K字节增加,在任务管理器中可以清楚地看到,调试了两天,所有该检查的地方差不多都查了个遍,无果,几近崩溃,甚至想放弃,没办法,生活还得继续,沮丧之后,还得查,看了无数的博客,由于本人不是计算机毕业的,之前学的是自动化,搞搞软硬件控制还行,但涉及到计算机软件内核的技术还是软肋,那怎么办,恶补呗,还是从线程查,所有线程全部关闭之后,发现里面还有四个线程,在VS2017线程里可以看到,
这里只有三个,其中一个mssocket线程已经被关闭了,按说程序已经没有我要求运行的任何代码了,就相当于个打开的对话框,静静地放在那里,没做任何事,这下应该不会有泄露了吧,但偏偏事与愿违,每分钟4K的泄露,一点不少,既然还有泄露,那继续,里面三个线程,其中ntdll.dll是微软内核线程,专门负责ring0底层动作,应该与泄露无关,即使有也不怀疑它,剩下两个,一个是主线程,一个是GDI,主线程已经不运行了,那剩下就是GDI,看还有哪个GDI在运行,在主程序里查了一晚上没查出,所有的GDI对象都被全局化了,不可能不断地需要新的内存,那到底是哪里出了问题呢,一个个模块查,最后发现在一个子窗口类中有这么一段:
HBRUSH CNotice::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
if (IDC_STNOTICE == pWnd->GetDlgCtrlID())//判断发出消息的空间是否是通知消息静态文本框
{
pDC->SetTextColor(RGB(255, 0, 0));//设置文本颜色为红色
pDC->SetBkMode(OPAQUE);//设置文本背景模式为透明
pDC->SetBkColor(RGB(127, 127, 255));//设置文本背景为蓝色
HBRUSH m_brush = CreateSolidBrush(RGB(127, 127, 255)); <======== 问题的根源
return m_brush;
}
// TODO: 在此更改 DC 的任何特性
// TODO: 如果默认的不是所需画笔,则返回另一个画笔
return hbr;
}
因为这段话是抄来的,也不知道是哪个网友还是哪个菜鸟写的,反正无处查正了,总之肯定不是自己写的,虽然主线程里没有任何动作,但这个窗口还是在显示,每次显示都有一个静态控件要重绘,而所有人都知道,CreateSolidBrush产生的画刷是必须释放的,不然,每次在OnCtrColor消息产生时,都会创建一个刷子,这个刷子是要消耗内存的,难怪2小时之后, 就自动崩溃了.原因找到.立即做一个全局的刷子,问题消失.
问题总结:1.之前总担心GDI对象全局化能不能行,会不会产生泄露或者其它意想不到的问题,现在可以肯定地回答 :不会,如果要频繁绘图,(特别是在OnPaint中绘图),最好用全局的GDI对象,我这里有五幅图片,用了五个CImage,每次绘图时直接调用就行了,不必每次load,还有CFont,HBRUSH,CPen,等,都可以全局化,少了每次使用的创建和销毁,运行效率更高,内存消耗也小了很多.
2.查内存泄露,光靠_CrtDumpMemoryLeaks()函数是不行的,必须看任务管理器,如果运行几分钟都没有看到内存增长,那恭喜你,大问题不会有了,但交互时操作的地方还要小心,不过那个可以控制在较小的范围.