加载中…
个人资料
南冠彤
南冠彤
  • 博客等级:
  • 博客积分:0
  • 博客访问:415,310
  • 关注人气:59
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

关于内存堆的问题

(2012-02-09 17:55:16)
标签:

杂谈

分类: CPP/C
1、free(delete)指针时遭遇_CrtIsValidHeapPointer
转自:http://blog.sina.com.cn/s/blog_7d52efd801013hpp.html

没找到转发直接复制过来了!!!今天研究了一大晚上...不知不觉的出现内存泄露,给大家提个醒...也方便我在遇到之类问题能够处理...下面是某个大虾的博文,分享下.free(delete)指针时遭遇_CrtIsValidHeapPointer

malloc和free在教材里不知讲了多少,今天实际用到一处,就出问题了。

案发现场是我用VC++在WINXP下编程,先看这一段代码
{
    BYTE    *pBuffer;
    pBuffer = (BYTE *)malloc(64);
    pBuffer = pBuffer + 32;
    free(pBuffer);
    pBuffer = NULL;
//第一段
先声明一个指针,然后分配64字节给它。操作指针向后移动32字节,最后把这个指针free掉并设为NULL。这样虽然操作时的数据还留在内存区域里,但已经没有指针指向该区域,数据可以被后面的程序覆盖了。看起来没错吧?恩恩好象是没错, 抓了三个同事来问,全票通过.  但在实际上呢, 运行时会跳出这个警告:

free(delete)指针时遭遇_CrtIsValidHeapPointer

而如果把代码改成
{
    BYTE    *pBuffer;
    pBuffer = (BYTE *)malloc(64);
    pBuffer = pBuffer + 32;
    pBuffer = pBuffer -32;
    free(pBuffer);
    pBuffer = NULL;
//第二段代码
就没错了。李博士的解释是:malloc的时候系统创建了一个链表之类的东西,里面包含了起始地址和长度;而free的时候根据参数中的指针进行判断,如果该指针符合链表中某一节的起始地址,那么就把这一节free掉,如果去free一个不在链表中的地址,就出错啦。

比较好的处理方式是这样
{
    BYTE    *pBuffer;
    BYTE    *pBytePtr;
    pBuffer = (BYTE *)malloc(64);
    pBytePtr = pBuffer;
    pBytePtr = pBytePtr + 32;
    free(pBuffer);
    pBuffer = NULL;
    pBytePtr = NULL;
//第三段代码

即保留malloc时候的初始地址,然后赋给另外一个临时指针。操作时使用后者。free的时候把保留的起始指针free掉,而临时指针只是个变量,置空就可以了。

文章到这里似乎就应该写完了,不过由于johnathan的帮忙, 突然有了下文。

注意警告框中的_CrtIsValidHeapPointer,在MSDN上查到这个函数的说明为:

Verifies that a specified pointer is in the local heap (debug version only).

int _CrtIsValidHeapPointer(const void *userData);

Parameter:
userData  Pointer to the beginning of an allocated memory block.

Return Value:
_CrtIsValidHeapPointer returns TRUE if the specified pointer is in the local heap. Otherwise, the function returns FALSE.

也就是说仅在debug版本中检查free参数是否为the beginning of an allocated memroy block.

那么就编译一个release版本,果然,运行通过, 没弹出什么警告或错误。但运行通过不等于正常无误,  博士认为这就是无声无息导致内存泄露的罪魁祸首. 即使release版里面也应该free起始地址.

那么结论就是:free掉非malloc起始地址的指针,在debug版本中会出错警告,而在release版本中不会弹出警告但仍然是有错的。所以,还是推荐使用第三段代码的用法



2、_BLOCK_TYPE_IS_VALID(pHead->nBlockUse错误原因

转自:http://blog.sina.com.cn/s/blog_7d52efd801013qpn.html

原因:1.内存泄漏;所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现“Debug Assertion Failed”的错误。

2.这个assert说明什么问题呢?说明有一块内存在被释放的时候,它的头部里面的信息已经被改掉了,和预期的不一样。内存分配的程序往往在被分配出的 内存块头部放上一些校验信息。这个信息内存的用户是不知道也不应该修改的。这样,在内存被释放的时候,内存分配程序就可以验对这个头部信息是否被改过了。 若被改过,就说明发生了内存corruption. 这种corruption有两种可能性:1)有人在内存越界写东西;或者:2)这块内存已经被释放了,又被重复释放了一次。 (在第一次被释放中,是内存分配程序改掉了头部信息)。

3.pHead_>nBlockUse就可能是空指针,或它指向的东西已经不存在了。



Re:Expression:_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)可能是什么问题?

http://blog.csdn.net/frankiewang008/article/category/839374


调试堆中的每个内存块都分配以五种分配类型之一。出于泄漏检测和状态报告目的对这些类型进行不同地跟踪和报告。可以指定块的类型,方法是使用对其中一个调 试堆分配函数(如 _malloc_dbg)的直接调用来分配块。调试堆中的五种内存块类型(在 _CrtMemBlockHeader 结构的 nBlockUse 成员中设置)如下所示: 

_NORMAL_BLOCK 
对 malloc 或 calloc 的调用将创建“普通”块。如果打算只使用“普通”块而不需要“客户端”块,则可能想要定义 _CRTDBG_MAP_ALLOC,它导致所有堆分配调用映射到它们在“Debug”版本中的调试等效项。这将允许将关于每个分配调用的文件名和行号信 息存储到对应的块头中。 
_CRT_BLOCK 
由许多运行时库函数内部分配的内存块被标记为 CRT 块,以便可以单独处理这些块。结果,泄漏检测和其他操作不需要受这些块影响。分配永不可以分配、重新分配或释放任何 CRT 类型的块。 
_CLIENT_BLOCK 
出于调试目的,应用程序可以专门跟踪一组给定的分配,方法是使用对调试堆函数的显式调用将它们作为该类型的内存块进行分配。例如,MFC 以“客户端”块类型分配所有的 CObjects;其他应用程序则可能在“客户端”块中保留不同的内存对象。还可以指定“客户端”块的子类型以获得更大的跟踪粒度。若要指定“客户端”块 子类型,请将该数字向左移 16 位,并将它与 _CLIENT_BLOCK 进行 OR 运算。例如: 
#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));
客户端提供的挂钩函数(用于转储在“客户端”块中存储的对象)可以使用 _CrtSetDumpClient 进行安装,然后,每当调试函数转储“客户端”块时均会调用该挂钩函数。同样,对于调试堆中的每个“客户端”块,可以使用 _CrtDoForAllClientObjects 来调用应用程序提供的给定函数。 

_FREE_BLOCK 
通常,所释放的块将从列表中移除。为了检查并未仍在向已释放的内存写入数据,或为了模拟内存不足情况,可以选择在链接列表上保留已释放块,将其标记为“可用”,并用已知字节值(当前为 0xDD)填充。 
_IGNORE_BLOCK 
有可能在一段时间内关闭调试堆操作。在该时间段内,内存块保留在列表上,但被标记为“忽略”块。 
若要确定给定块的类型和子类型,请使用 _CrtReportBlockType 函数以及 _BLOCK_TYPE 和 _BLOCK_SUBTYPE 宏。宏的定义(在 crtdbg.h 中)如下所示:

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

堆内存与栈内存

在标准C语言上,使用malloc等内存分配函数获取内存既是从堆中分配内存,而在一个函数体中例如定义一个数组之类的操作是从栈中分配内存。在C++语言中则使new动态的分配一块内存,然又必须用delete来进行销毁,否则将导致内存泄漏。

  从堆中分配的内存需要程序员手动释放,如果不释放,而系统内存管理器又不自动回收这些堆内存的话(实现这一项功能的系统很少),那就一直被占用。而栈内存在函数体内一直存在,你无法丢掉,在离开函数体后,立即被销毁,你无法挽留。

  如果老是申请堆内存,而不释放,内存会越来越少,很明显的结果是系统变慢或者申请不到新的堆内存。而过度的申请栈内存(可以试试在函数中申请一个1G的数组!),会导致栈被压爆,结果是灾难性的。栈都爆了,谁知道函数会跑到哪里去?!

     我们掌握堆内存的权柄就是返回的指针,一旦丢掉了指针,便无法在我们视野内释放它。这便是内存泄露。而如果在函数中申请一个数组,在函数体外调用使用这块栈内存,结果谁也无法预测。

  申请栈内存的时间是基本确定的。核心只要栈指针的偏移和对它进行初始化(如果必要的话)。但堆中的时间是无法预测的,这是内存管理器的任务,跟他的算 法和当前的堆内存结构有关,也许很快,也许很慢。两者在这里不存在可比性,尽管大多数情况下栈内存的申请的确比堆内存要快。

  申请的时候,栈内存几乎等同与申请大小,大多数的偏差是内存对齐引发的几个字节影响。但对内存但实际也会会相差很大(这主要源于内存管理器算法)。常 见的内存管理都是按照基本内存块大小来分配的。在一个基本单元是1024Byte的管理器上,哪怕你只申请一个Byte,它也会给你1024个。频繁的申 请小的堆内存是不明智的。

栈内存和堆内存的大小也没有可比性。栈内存大小跟IC,编译器,OS相关。而对堆内存的大小一般是内存管理器说了算。X86上Windos下VC编译的代 码只是N种情况之一。并不具备多少代表性。至于磁盘是否能为堆内存撑腰,完全在于你使用的内存管理器和IC是否支持虚拟内存。

  从更底层一点来说,堆和栈只是对他的可见内存(可能是物理内存)的使用方式。如果我们你有1G的内存,我们可以给分256K给栈,然后拿500M给内 存管理器。于是前者成了栈内存,后者成了堆内存。当然,如果你要给栈800M,对256个字节也没问题。如果我有块干净的IC和系统,所有的东西都自己起 步,就可以这样干。只需要拿一块单片机系统或者直接在VC上模拟也可以。

     malloc只是标准C种实现动态内存申请的一种方式而已。它和堆内存申请并不等价。在我们自己内存管理器种,完全可以是任何名称(不考虑编程规范和接口标准的话)。



0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有