记一次Qt Creator编译产生CrtIsValidHeapPointer的错误

来源:互联网 发布:淘宝店怎么转让给朋友 编辑:程序博客网 时间:2024/06/14 00:39

分类: QT 108人阅读 评论(0) 收藏 举报

目录(?)[+]

1问题描述

我用Qt Creator在ubuntu下面编译了一个dll(姑且称为a.dll)吧,和一个exe(姑且程序b.exe)吧,运行是好好的。

但是迁移代码到windows上面的时候编译也正常,但是在我关闭窗口的时候,程序断点在delete语句处,并出现了下图的错误。


然后我在C::\下搜索dbgheap.c文件,最终dbgheap.c文件的的路径是(C:\Program Files (x86)\Microsoft Visual Studio 8\VC\crt\src\dbgheap.c)

我又定位到1279行,那里的代码如下:

[cpp] view plaincopyprint?
  1. <span style="color:#ff0000;">        /* 
  2.          * If this ASSERT fails, a bad pointer has been passed in. It may be 
  3.          * totally bogus, or it may have been allocated from another heap. 
  4.          * The pointer MUST come from the 'local' heap. 
  5.          */</span>  
  6.         _ASSERTE(_CrtIsValidHeapPointer(pUserData));  

这里的翻译是,该指针可能是伪造的,或者是在其他的堆中分配的。该指针必须在本地堆中。


然后我对照的自己的代码发现了问题,我是在exe通过new分配的内存,但是在DLL中通过delete释放该内存的。

并且又借鉴了网上的人的例子知道我这程序的dll有一个堆,exe也有一个堆。并且在一个堆中分配的内存,只能在该堆中释放。我的代码是错误的,那该怎么办呢?


2. 例子测试

然后我通过VS2005写了一个例子

在exe中

[cpp] view plaincopyprint?
  1. BOOL CTestControlDlg::OnInitDialog()  
  2. {  
  3. //...  
  4.     m_p111 = new CMemoryTest;  
  5.     PTESTDATA pData1 = new TESTDATA;  
  6.     pData1->a = 11;  
  7.     pData1->b = 67;  
  8.     m_p111->m_lstTestData.push_back(pData1);  
  9. //...  
  10. }  
  11. void CTestControlDlg::OnBnClickedTest()  
  12. {  
  13.     delete m_p111;  
  14. }  

在dll中

[cpp] view plaincopyprint?
  1. #include <list>  
  2.   
  3. typedef struct tagTestData  
  4. {  
  5.     int a;  
  6.     char b;  
  7. }TESTDATA, *PTESTDATA;  
  8. typedef std::list<PTESTDATA>        LstTestData;  
  9.   
  10. class LIBTOOLKIT_API CMemoryTest  
  11. {  
  12. public:  
  13.     CMemoryTest(void);  
  14.     ~CMemoryTest(void);  
  15.   
  16.     LstTestData     m_lstTestData;  
  17. };  
  18.   
  19. #include "StdAfx.h"  
  20. #include "MemoryTest.h"  
  21.   
  22. CMemoryTest::CMemoryTest(void)  
  23. {  
  24. }  
  25.   
  26. CMemoryTest::~CMemoryTest(void)  
  27. {  
  28.     LstTestData::iterator it = m_lstTestData.begin();  
  29.     PTESTDATA p = (*it);  
  30.   
  31.     delete p;  
  32. }  
这次我在exe中分配内存,但在dll中释放内存并没有错误呢?

为什么我在Qt Creator这样做就有错误呢?


3. CRT知识补充

看过我转载的文章我知道,在Windows下面的程序运行都是需要引用C runtime library的,那么这个C runtime library是什么?从哪里可以得到?

我先回到第2个问题,如果你安装了vs2005,那么可以在C:\Program Files (x86)\Microsoft Visual Studio 8\VC\redist\x86\Microsoft.VC80.CRT\ 得到你所需要的C runtime library。

至于第1个问题,我简单说下对于Windows系统而言,有CRT---->Win32 API ---->MFC这样一个关系,及MFC是基于win32 API开发,而Win32 API又是基于CRT开发出来的。

所以我们所有只要使用C/C++相关语言特性的代码都要用到CRT库。

至于   详细的有关CRT的介绍,可以 单击这里:(http://www.cnblogs.com/chio/archive/2007/11/26/972152.html)


好了,既然CRT是以DLL的形式提供给用户使用,那么必然有动态链接和静态链接,并且VS2005对应工程的属性设置里面也有,我贴张图大家来看下:



这里我们看到Runtime Library其实就是指的C Runtime Library,一共有四种配置,/MT, /MTd, /MD, /MDd,至于该4中特性,在下面的“文章2”是我转载的该4中特性的区别。

当你看了我转载的2片文章后,你应该也就该知道,我们这里最好的方法是使用/MD来编译我们的程序,


4.  Qt Creator中寻求设定/MD的方法

但是Qt Creator里面似乎并不是很容易的设置/MD属性,怎么办哪?哭

既然/MD是编译选项,肯定有地方可以设置,不知道大家记得没有在VS2005里面Command Line,我们会看到/MD,那么在Qt Creator里面也一定有,因为我在Windows下的Qt Creator是使用的VS2005的编译器。

然后我在Qt Creator下面查找,最后在“编译输出”一栏找到了如下的代码

[cpp] view plaincopyprint?
  1. C:\Qt\qtcreator-2.6.0\bin\jom.exe -f Makefile.Debug  
  2. cl -c -nologo -Zm200 -Zc:wchar_t- -Zi <span style="font-size: 18px; color: rgb(255, 153, 0);">-MTd</span> -GR -EHsc -W3 -w34100 -w34189 -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQUIZCENTER_LIBRARY -DQT_DLL -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX  
赫然耸立这一个大大的MTd,这并不是我们想要的,我需要找到这个make文件。

我必须得知道这个make在什么地方,如何生成的?

我现在知道当我们运行qmake的时候就生成了该make。运行qmake如下图。



生成的makefile文件很可能是这样


然后我进入MakeFile.Debug找到了下面的代码,请看图:


这里我手动将其改为-MDd,同时我也将exe那边改为-MDd,结果在编译这个DLL和exe。

即使我在exe里面分配内存,在DLL里面释放也不出现错误了,因为他们使用的是同一个堆。


至于如何在Qt Creator工程的.pro文件里面设定-MTd和-MDd,本人暂时还不太清楚,抱歉了。。。

不过我知道的时候会补上的。。。。



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

转载例子介绍:

// 转载文章1: _CrtIsValidHeapPointer的原因

地址:http://blog.sina.com.cn/s/blog_5119a7f90100yeqm.html

文章一

 

从CrtIsValidHeapPointer的实现代码处我们可以得知,这个函数不仅检查了空指针的情况,更重要的是检查了指针地址的有效性。

2.1 我遇到的问题: 

释放内存时,弹出assert,报错的函数就是上面提到的这个:CrtIsValidHeapPointer。
可是为什么会报错呢?CrtIsValidHeapPointer的注释上说明了情况:如果要释放的内存地址不是在当前控制的堆的地址范围内,也会报这样的错误。

2.2 问题分析: 

1、我的实现是在exe中调用dll的一个方法,这个方法里面里面会分配若干内存,并将数据拷贝到新分配的内存中,传递出去;之后我会在exe中对这部分内存进行释放。
2、同一个进程,难道还有两个堆?如果你的工程Runtime Library选择的是MT(使应用程序使用运行时库的多线程静态版本)类型,那么,是的,确实是这样,dll和exe是分别的两个堆。
3、也就是说,dll中分配的内存只能在dll中进行释放,否则CrtIsValidHeapPointer函数就会报错

起初觉得怎么这样呢?但考虑到不用dll的实现者是不一样的,那么他本身的选择的运行时库的链接方式也可能不一样,非要把他们都放在一个堆上操作反而会使问题变得复杂。CRT的这种处理确实更为合理。

2.3 解决方法: 

既然谁分配谁释放,而我要分配的内存大小只有dll自己知道,我只好将分配内存的事情放在了dll中,然后dll再提供一个释放内存的方法。类似:
1、allocAndGetData(CData** ppData);    // 我不喜欢这个函数名
2、freeData(*pData);

 

文章二

_CrtIsValidHeapPointer()在Debug模式下测试一个地址在本地的堆内存中,如果采用共享的方式链接C运行时库那么不会出现什么问题,但是如果采用静态的方式链接C运行时库,那么调用该DLL中的函数时就可能出现问题。提示一个错误.

例如:有两个DLL文件 a.dll ,b.dll 且a.dll采用静态的方式链接C运行时库。

如果 a.dll 中导出一个函数 CString aFun(),按值返回一个字符串。

如果 b.dll 中调用该函数如 CString str=aFun();

b.dll调试运行时将出现错误,提示_CrtIsValidHeapPointer()出错.

在MSDN中查找该函数说明知,当一个DLL采用静态的方式链接到C运行时库时,会创建一个相对于该DLL的堆(Heap),而如果采用共享的方式链接到C运行时库的时候则使用的是应用程序的堆内存。而_CrtIsValidHeapPointer()在 DEBug模式下将确保传入的地址在本地的堆内存中,这样问题就明显了:

a.dll采用静态链接C运行时库,所以会创建一个相对于该DLL的堆内存,这样a.dll中分配的临时变量均在该堆中分配。

再看a.dll中的函数aFun()以值的方式返回了一个字符串,执行到return语句时会创建一个临时的变量作为返回,但此临时变量内存却在a.dll的堆内存中,当在b.dll中调用时,语句CString str=aFun();完成时该临时变量的CString析构函数被调用,会释放内存空间,但此时_CrtIsValidHeapPointer()检测到该内存地址不在本地的堆内存中,产生一个错误。但忽略该错误程序有时对正确运行。

解决的办法是将a.ll采用共享的方式链接到C运行时库。

 

这个错误花了很长时间才找出来,哈哈。。。。

文章三

这是因为dll拥有一个独立于应用程序(调用它的exe)的本地堆,调用过后dll占用的空间即被释放,先前new的内存地址即无效,那么解决此问题时要将dll文件中动态申请内存的时候使用HeapCreate函数而不能使用new操作符,具体说明详见MSDN,以下

/////////////////////////////////////

// 转载文章2  MD(d)、MT(d)编译选项的区别

地址:http://www.cnblogs.com/cswuyg/archive/2012/02/03/2336424.html

3.1 MD(d)、MT(d)编译选项的区别

3.1.1、编译选项的位置

以VS2005为例,这样子打开:

1)         打开项目的Property Pages对话框

2)         点击左侧C/C++节

3)         点击Code Generation节

4)         右侧第六行Runtime Library项目

3.1.2、各个设置选项代表的含义

编译选项

包含

静态链接的lib

说明

/MD

_MT、_DLL

MSVCRT.lib

多线程、Release、DLL版本的运行时库

/MDd

_DEBUG、_MT、_DLL

MSVCRTD.lib

多线程、Debug、DLL版本的运行时库

/MT

_MT

LIBCMT.lib

多线程、Release版本的运行时库

/MTd

_DEBUG、_MT

LIBCMTD.lib

多线程、Debug版本的运行时库

 

简单的说:

(1)/MD,表示运行时库由操作系统提供一个DLL,程序里不集成。

(2)/MT,表示运行时库由程序集成。

 

3.2、/MD、/MT的选择

      3.2.1、为什么选择/MD,不选/MT?

         (1)程序就不需要静态链接运行时库,可以减小软件的大小;

         (2)所有的模块都采用/MD,使用的是同一个堆,不存在A堆申请,B堆释放的问题。

      3.2.2、为什么选择/MT,不选择/MD?

         (1)有些系统可能没有程序所需要版本的运行时库,程序必须把运行时库静态链接上。

(2)减少模块对外界的依赖。

      3.2.3、多个模块,必须选择相同的运行时库。

3.3、选择/MT需要解决的堆空间释放问题

         不同的模块各自有一份C运行时库代码、或者根本没有C运行时库,导致了各个模块会有各自的堆。如果在A堆中申请空间,到B堆中释放就会有崩溃,在模块A申请的空间,必须在模块A中释放。

         附录的DLL以及DLLUser代码,以STL的string为例,通过修改编译选项验证了这个问题。string在赋值的时候需要释放掉原来的空间,然后再申请新的空间存储新的内容。

 

3.4、选择/MD需要注意多个模块使用不同版本运行时库的问题

     (2012-9-17补充)

     多个dll被一个exe LoadLibrary加载,如果这些dll使用的运行时库是不同的,那么可能出现加载失败,原因可能是旧版本的运行时库已经在了,而某个dll它需要的是新版本的运行时库,旧版本不符合要求。

     如果工程里所有的模块都是自己写的或者可以完全控制的,那么这个问题不难解决,只需要在工程设置里都设置/MD,然后在相同的环境下编译一次就行。但是假如这个模块是外界提供的呢?

     可能存在这种情况:A动态库使用了B静态库,B静态库使用了C动态库,B静态库是外界提供的,我们要使用它,但无法修改它,我们也无法接触到C动态库。如果C动态库使用的运行时库版本跟编译A动态库的本地使用的不一致,那么A动态库里的嵌入信息就会记录两个不同版本的运行时库,它被加载的时候,可能会选择版本新的。假设A动态库被一个exe LoadLibrary加载,而这个exe本身的运行时库是旧的,这样就会导致A动态库加载失败,即便把新的运行时库拷贝到目录下也不行,因为exe这个进程已经加载了那个旧的运行时库。这时候必须使用manifest文件指定嵌入到A动态库里的运行时库为某个版本,忽略掉C动态库使用的运行时库版本。

     这个问题挺复杂的,我心思没去验证windows的PE文件加载会对运行时库做什么样的优先选择、运行时库在静态库里的记录…。只要记住,给外界使用的版本避免使用/MD(会导致膨胀?)。

3.5、参考资料

1、微软关于MT、MD等的详细介绍

http://msdn.microsoft.com/en-us/library/2kzt1wy3(v=VS.71).aspx

2、不要出现A模块申请,B模块释放的情况

http://www.cnblogs.com/minggoddess/archive/2010/12/15/1907179.html

3、运行时库有哪些版本

http://www.cppblog.com/MichaelLiu/articles/10607.html

4、CSDN上关于堆空间释放的讨论

http://topic.csdn.net/t/20010112/09/57983.html

http://topic.csdn.net/t/20031009/17/2338051.html

http://topic.csdn.net/u/20090502/00/bf1602e3-ddf5-49b0-af81-8a23383f9ddc.html

http://blog.csdn.net/blz_wowar/article/details/2176536

5、不同模块不同的堆

http://www.cnblogs.com/WengYB/archive/2011/08/18/2144727.html

 

6、因为运行时库版本问题导致加载失败的分享

http://blog.csdn.net/dev_yarin/article/details/6768373

http://blog.163.com/henan_lujun/blog/static/19538333200611485511640/

 

附录:

下载地址:http://files.cnblogs.com/cswuyg/Test_MD_and_MT.rar


原文出自http://blog.csdn.net/oldmtn/article/month/2011/02/2

原创粉丝点击