009-09-16 19:02 Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed

 方法一:若是你是C++程序员,若是你写过一个很复杂的程序,若是你常常碰到莫名其妙的崩溃问题。那么你就有可能遭遇了野指针。若是你比较细心,注意了Debug output输出窗口的话,那么你就有可能注意到这样一行提示:程序员

HEAP:   Free   Heap   block   xxxxxxxx modified   at   xxxxxxxx after   it   was   freedwindows

网络上关于这个问题提问的人很多,可是真正给的出答案的却少之又少。在网上搜索了几天,终于发现了这个问题的解决之道。下面我来介绍一下。网络

 

GFlags是windows debug tools 工具包下的一个工具,在Windows 2000的Resource Kit中也能够找获得。用来设置一些调试属性,整体上分为3个级别System, Kernel, 和 Image File。多线程

咱们设置好Path环境变量,将其指向Debug tools工具的目录下。app

输入以下命令行:socket

gflags /p App.exe /fullide

或者 pageheap app.exe /full函数

该命令行会在注册表里设置一些调试参数,使内存在使用的时候加入了保护机制,因此一旦内存写越界,或者发生野指针的问题,都会致使一个中断。由此,你就能够肯定问题到底出在哪里了。工具

方法二:测试

多线程与内存(heap)

关于多线程中的内存问题,必需足够地重视,不然,程序老是会出现莫名其妙的错误。若是幻想某些状况“应该”不会出现,或者是认为某些步骤是按照正常的顺序下来的,那就错误大大地,由于有一个简单的事实,就是你没有遵照事实,想和CPU对着干!

xx SDK的报错退出问题,从去年直到如今都没有获得有效的解决,近段时间,被频繁地爆露出来,是到了不起不直面的时候了,因此,前两周,去了xx N次,直到如今,还不能肯定问题是否真地解决了

SDK的现象:
在本身的测试demo上没有问题,而在平台上就有问题
后来的测试证实,不是demo没有问题,而是原来的demo只是在跑本身测试的设备,其数量少,环境不真实,实际的状况没法获得有效的测试,因此,demo不会有任何问题

直接运行程序,就是出现调试的那种错误,在平台的vc debug模式下,出现的是触发了用户断点的错误。在SDK的vc debug模式下,爆露了错误的本质:
HEAP[pingtai.exe]: HEAP: Free Heap block 2be3000 modified at 2be3200 after it was freed
这种错误过去从没有遇到过,经查,代表是由于堆被破坏而产生错误。种种迹象代表,是由于某个内存被删除以后,其内容又被修改了,这样就是破坏了堆,就出现了这种错误,模拟程序以下:

void fun1()
{
char *p = new char[128];
delete[] p;

strcpy( p, "abcdefghijklmnopqrstuvwxyz" );
}

在我模拟的时候,其状况以下:
1. 若是我strcpy()的字节少于13个字节,则不会报任何错误。此状况并不绝对
2. 当fun1()返回时,并不出错,当程序退出时,报错
3. 在fun1()返回后,再调用其它任何函数,即会报错

为方便后面的叙述,把错误的描述以下:
HEAP[<exe>]: HEAP: Free Heap block <addr1> modified at <addr2> after it was freed

对模拟程序的报错进行分析,得出以下结论:
对内存<addr1>进行了删除,而后,又修改了内存<addr2>处的内容,其中,<addr2>是被包含在<addr1>中的,形式以下:
|------------------------------|
| <addr1> | <addr2>           |
|------------------------------|
在模拟程序中,<addr1> = p,<addr2> >= p 且 <addr2> < p + 128
在真实的内存中,<addr1>并非等于p,而是会比p小,其不表示程序代码中的任何一个内存,而是在new时,用于保存p的内存而产生的内存指针,该指针由windows保存和维护(注:我的认为,经过计算,应该是能够获得p的地址),实际的模式以下:
|------------------------------|
| <addr1> | p                 |
|------------------------------|
<addr1>是windows占用的内存,而p是程序占用的内存

既然内存的操做已经明确,并且也知道了如何产生了这种错误,那么就要根据<addr2>来找是程序中哪一个地方出现了这种状况,采用倒推 的方式,即首先根据<addr2>找到是哪一个内存被删除后又从新进行了附值(或者是其中的某处被从新附值),而后再根据状况来使删除和附值不 要冲突

程序的顺序应该没有问题,由于都是删除后就再也不进行附值操做,或者是其它任何使用的可能了,因此,从须要分配内存(主要是char*类型的内存分 配)变量开始找起。把全部new出来的char*指针地址及其长度打印出来,从上万行的打印信息中一个一个查找,却发现没有<addr2>
这就奇怪了,难道是程序的执行顺序出现了问题?不可能吧
把全部new出来的类地址及其内存范围进行打印,再一行一行地仔细找,汗......
居然是CAccept类出现了这种状况!难道其删除以后,还有被使用的状况发生?根据以上内存的分析,确定是有这种使用状况存在,不然....,找!
由于CAccept的类使用较少,找起来也比较容易,主要就在两个地方使用,一个是完成端口的死循环中,一个是任务检查的死循环中,确定是这两个地方的使用有冲突。
完成端口的工做模式:
while( true )
{
if ( !getcompleteio() )
break;
if ( isaccept() )
{
CAccept *pacc = getaccept(); // 从列表中获取
// 使用pacc,其中有附值操做
... ...
// 删除pacc
SAFE_DELETE(pacc)
}
else
{
// 数据收发及socket端口的其它消息处理
}
}

任务检查的工做模式:
while ( true )
{
wait(5000); // 等待5秒钟,开始任务检查
lock();
while ( not_end_accept_list )
{
CAccept *pacc = nextaccept();
if ( pacc->tooLongTime )
SAFE_DELETE(pacc) // 删除 pacc
}
unlock();

// 链接检查
...
}

对accept的操做子函数(好比createaccept()、getaccept()、deleteaccept()等),都是被放入临界区中 的,这里仔细检查发现,完成端口中对CAccept的使用没有被放入临界区中,虽然其getaccept()中有临界区的操做,虽然任务检查中对 accept列表的操做也是在临界区中进行的,那么,问题确定就是出如今了这里。为了验证,对任务检查处的accept删除和在完成端口中对accept 的使用进行信息打印,发现,当完成端口中尚未对其获取的CAccept使用完时,已经被任务检查的循环删除了!
这种状况在正常状况很难出现,为 了使它容易出现,对临界区的操做加了延时,即人为使每一个操做都等待较长的时间(原来是没有任何等待的),好比100毫秒,或者50毫秒等,这样,可使很 多的操做都被挂起,在设备的链接达到必定多的时候,这种挂起的操做会越集越多,采用这种方法,可使一些问题较容易地复现出来
找到问题了,解决起 来应该是比较容易的了(有些但是棘手的,好比完成端口中else分支里的socket消息的处理),只须要在完成端口中把对CAccept的使用也放入相 应的临界区中便可,修改完毕,测试,再也没有CAccept的堆问题了,至此,该部分的问题已“完美”地被解决了,那个痛快啊---------,就别提 了... ...

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
还有两个问题,一个是关于上层对SDK的调用问题,一个是SDK内部的问题。
这两个问题,都和以上的问题的本质同样,就是删除了后还在使用!
1. 上层对SDK的调用
SDK 已经通知了上层设备断开,可是,上层在获得该消息以后,居然还在调用相应的接口。这自己不成问题,由于上层调用SDK的时候,SDK会从相关的设备列表中 获取相应的链接,而后拿这个链接对设备进行操做,若是是该设备已经断开,那天然就会从相应的列表中删除掉,那么在上层调用的API中获取链接时,确定是获 取失败,SDK在判断后,天然直接返回失败。没错!你说得太对了,就是这样。...... 停!你想,仔细地想想,而后再往下看
================================
在 SDK中,不知道上层对SDK的调用和消息接收,是在一个线程中仍是两个线程中,那么,SDK无从知道上层对SDK的消息处理和API调用的前后顺序,有 一种状况,应该是比较容易理解的,当SDK向上层进行了不少的消息通知,上层多是会把这个消息放入消息队列中,而后一个一个地慢慢处理,其处理的某一个 消息,也许是10秒钟以前的消息(有点夸张),若是是这样,并且若是恰好这个消息就是链接断开的消息,那么在其处理该消息以前的3秒钟,用户要对该设备下 发命令,用户看到的结果就是,该设备在线,即下发命令失败,并且显示的错误消息是设备不在线!这样,程序也不会出错,由于7秒了,过久了,SDK应该是已 经把这个链接从链接列表中删除了。
但不幸的是,并不老是这种状况,而是有一个糟糕的状况可能会出现。当API函数中获取到相应的链接后,正在向设 备下发命令时,链接列表被任务检查中的链接检查循环删除了(好比有1分钟都没有收到任何数据),这时,若是下发命令的函数中是读取,而不进行任何的附值操 做,则出现的错误,应该是内存非法读的异常,不然,出现的错误,应该是和上面同样的堆错误。这种错误,也许解决起来比较容易,好比在使用时,也放入临界 区,这样固然可行,只是当有100个API时,你就必需得进行100次临界区的操做,这不算重要,更重要的是,因为你在最外层增长了临界区的操做,那么在 SDK的内部,必须要当心地保证临界区的操做不会产生冲突,不然,就会发生程序无反应的状况,须要使用任务管理器来结束的后果。若是其中有附值操做,这也 许是惟一的一种解决方法,但若是是没有附值操做,则可能会比较简单,好比,我只在那时出现了这个错误的API外层,增长了 try...catch()...,这样,若是执行的中间其实例被删除了,则该API直接返回失败。具体的解决方案,能够根据实际状况的不一样,进行不一样的 处理,这里,只是阐明我本身的想法而已
2. SDK内部的另一个问题
和CAccept彻底同样,只是其复杂度至关大,因为其几乎涉及到程序的全部地方,因此,解决起来至关棘手,并且,因为其和要上层通信,则一不当心,就会使临界区冲突(除了上层的代码以外,SDK中的代码都被放入临界区):
[收到socket消息] --> [通知上层] --> [上层调用SDK API] --> [API调用SDK内部的函数]
这个过程当中,由于SDK通知上层后,须要等到上层返回,才进行下一步处理,上层须要调用API的返回后本身才返回,而API调用内部函数时候,可能须要先进入临界区才能操做,如此便出现了临界区的冲突。
因此,在处理收到的socket消息时,被放入临界区是比较危险的作法,因此,这里若是要找到一个好的解决方案,比较棘手

因为时间问题,暂只浅谈之.

相关文章
相关标签/搜索