当前位置:项目开发->项目经验->HEAP CORRUPTION DETECTED

原创版权标志HEAP CORRUPTION DETECTED

作者:codexia  发表时间:2016/12/6 17:57:32  阅读:
[摘要] HEAP CORRUPTION DETECTED:after Normal block(#XXXX)at 0xXXXX. CRT detected that the application wrote to memory after end of heap buffer.
文章来源:C++技术网原创文章版权所有,会员文章禁止转载。非会员文章转载做好本文超链接即表示授权转载。通过文章下面的分享按钮可以自由分享所有文章。

    今天在调试程序的时候,一直蹦这样一个错误提示:

HEAP CORRUPTION DETECTED:after Normal block(#XXXX)at 0xXXXX

   错误提示:

HEAP CORRUPTION DETECTED:after Normal block(#XXXX)at 0xXXXX.
CRT detected that the application wrote to memory after end of heap buffer.
   好吧,第一次遇见。这个错误还真是第一次遇见哦。不过不用慌,看看提示就可以解决问题了。因为错误提示很清楚。

    翻译一下错误提示:

HEAP(堆) CORRUPTION(不正确,破坏) DETECTED(被检测到):after(在什么之后) Normal(正常的) block(块,内存块)(#XXXX)at 0xXXXX.
CRT(C/C++运行时库) detected that the application wrote to memory after end of heap buffer.检测到应用程序在堆所在范围的后面的位置写内存。

    看完翻译,大致了解到问题,就是堆被破坏。堆被破坏,基本是发生在释放内存的时候。分配内存是不会有堆被破坏的情况的。

    堆被破坏的情况:释放了不属于自己的内存、释放超过自己拥有的内存量。当然,如果释放的量少于拥有的内存量,会导致内存泄漏,一般会有语法错误。比如直接不加[]来释放指向一个数组的指针。例如:


char *p = new char[100];
delete p;//错误用法

     一般来说,我们还是不容易出现释放超过我们自己拥有(分配)的内存量的。因为我们直接分配,直接释放,就是等量的操作。但是今天确实让我碰见了不等量的内存释放,导致了堆被破坏。

     我们来看堆被破坏情况的代码用例:



#include "stdio.h"
void main()
{
 char* p = new char[10];
 sprintf(p,"hello world");//正常格式化,但是超出了分配的内存
 printf(p);//正常打印
 delete []p;//崩溃
}
    这个示例代码看起来很明显,就是sprintf格式化的时候,超出了new的内存大小。找到了原因,你处理一下sprintf或者扩大分配的内存大小即可。


    而释放不属于自己的内存的情况:


#include "stdio.h"
void main()
{
 char* p = new char[10];
 sprintf(p,"hell");
 printf(p);
 delete []p;//正常释放
 delete []p;//释放了不属于自己的内存
}
    以上两种都会破坏堆,所以就会出现崩溃情况。第二种是最常见的内存错误了。


    虽然问题是可以解决了,但是你还是不知道为什么会崩溃,其实还不够哦。你理解的崩溃就是内存越界了,实际上并么有这么简单。

    为什么sprintf越界不崩溃,为什么delete才会崩溃?是delete释放了12个字节,还是释放了10个字节呢?如果释放的是10个字节,又怎么崩溃了?如果释放12个字节,指针不是指向的是10个字节吗,指针指向大小怎么就变了?

    如果你把这些都理清楚了,这个问题就彻底搞定了。否则就只是完成任务式的解决了问题。这样就很难提升自己,遇到这些问题的时候是提高的好时机哦。

    那么我分享下我研究的结果,不一定完全正确,但是可以给你一个开阔的学习思路:

    p指针是不会改变指向的内存的大小的,除非你转换或者强制转换指针类型之后,指针指向的内存位置和内存大小就变化了。我们这里只是使用了指针而已,谈不上改变指针的大小。所以指针还是释放了10字节。(当然,因为p指针是char*指针,所以结尾是以\0来识别的,如果delete基于\0来检测内存的大小,那么p指针的大小就被改变了。当然,这个都是有可能的,就看具体如何实现了。我就不深入研究delete的释放char*指针的策略了。括号中的猜测,有兴趣的朋友可以研究一下。)

    既然只释放10字节,那怎么还崩溃呢?我们的测试环境是VS2010。没有道理呀。所以,你需要换个角度想了。为什么sprintf越界不崩溃,为什么delete才会崩溃?崩溃是谁提示的呢?

    我们先想想,崩溃提示是怎么回事。错误提示里明显提示了CRT检测到了问题。自然错误提示是CRT提示的,CRT就是C/C++运行时库,那么C/C++的库函数也就是提示的人。sprintf越界格式化没有提示错误,只能说明sprintf不检测内存。而delete是对内存的释放操作,非常的关键。所以为了保护内存安全,所以在Debug模式下,对内存做了更多的检测,防止出现内存错误。所以,你可以将这个代码放在Release模式下运行,不一定崩溃,因为越界的内存不是太多。

    不一定崩溃的原因是这样的:如果你分配的内存不是很规整,内存分配的时候可能是按照块来分配(因为没有深入研究CRT的分配内存的具体策略,所以我只是理论上分析一下),以免内存使用低效,产生太多的碎片。所以,如果你只分配很小的内存或者不规整的,分配内存会多余你自己的需要的长度,其他程序无法分配你这个多余的长度。这个叫做块内碎片。既然如此,这个块内碎片其他人无法使用,那么你超过了一些,刚好占用了这个碎片,自然也不会危及其他程序,所以不会崩溃。但是如果越过了这个大小,越到了其他程序的内存范围,就会被系统阻止,而崩溃了。

    另一个不崩溃的就是,产生块间碎片。也就是块分配的大小时间还是有碎片,你占用了碎片,也不会崩溃。

    所以Debug模式下,delete操作符已经检测了内存的使用,你只申请10字节,却在格式化的时候越过了10字节,所以delete检测到了这个。Release模式则需要程序员自己确保Debug模式已经正确的时候使用。

文章来源:C++技术网原创文章版权所有,会员文章禁止转载。非会员文章转载做好本文超链接即表示授权转载。通过文章下面的分享按钮可以自由分享所有文章。



  • codexia    2016/12/7 17:22:16回复
    今天填了一天的坑,就是因为sprintf格式化引起的崩溃。使用string替换了sprintf进行了字符串的拼接,内存不再越界,所以就没有问题了。这个是典型的问题。

返回顶部

在线提问
问题标题:
问题描述:(简陋的描述会导致问题被最后回答、没有针对性回答甚至无法解答。请确保问题描述的足够清楚。)

C++技术网群幕群聊

弹幕