前言
- 最近在开发调试基于RT-Thread 的程序时,遇到一个比较奇怪的死机问题,后来经过一步步排查,终于发现是动态内存申请的数据结构没有清零引发的死机
排查方法
- 由于没有单步调试的手段,就通过 打印调试LOG 与
#if 0 A_CODE #else B_CODE #endif
条件编译的方式,通过注释部分代码等方法,快速缩小问题的排查范围 - 最终逐步排查,定位在内存资源释放的函数部分
xxfree
,也就是程序执行完了,释放动态申请的内存时,触发了死机,注释掉这部分代码,不死机了,不过这样会造成【内存泄露】 - 通过查看代码继续排查,
xxfree
函数设计的没有问题,所有指针都有判空操作,也就是只会free
非空的指针,但是这种必现的死机,就几段内存free
的代码,排查应该容易,所以增加了些LOG,并且把free
时的指针地址也打印出来,确认是否free
了不属于自己的指针或者重复free
指针。
问题定位
- 在
xxfree
函数 增加多行 LOG后,再次运行死机后,我看了一下死机后的 LOG 信息,瞬间找到了方向,原来free
的指针不是NULL,而是0xffffffff
,怪不得会死机! - 通过全局搜索这个指针成员,竟没有发现赋值的地方,也就是没有赋值,希望它为
NULL
,而它实际上是0xffffffff
。 - 这个指针附属于一个大的结构体,这个结构体是通过
malloc
方式动态内存申请的,但是没有【清零】操作,并且后续的操作中,这个指针成员由于某种条件下,没有代码执行到赋值,默认【野指针】。并且这个板子动态申请的内存默认不是0x00
,而是0xff
,所以 free 了 非空的野指针触发死机 - 解决方法:
malloc
后的数据结构,增加memset
清零操作,这样保证指针成员默认为NULL
,如果是野指针, 并且free
时不为NULL
,会造成释放内存操作异常,大概率触发死机。
触累与排雷
- 继续排查驱动里面,有几处也是通过动态内存申请
malloc
数据结构 后,也没有memset
清零,并且里面有 链表成员,链表也没有初始化,并且【侥幸成功】的挂上了 链表头head
上,没有触发死机。 - 由于 资源释放这个操作一般都发生在【游戏结束】后,也就是代码不运行了,所以没有特别的注意,即使链表的成员没有清零操作,指针为野指针,也能 插入到 链表头,但是当多次这种操作后,就会触发内存异常,因为没有【清零】的内存空间,数据可能不是零,在地雷的旁边走,就有踩上去的机会!
- 加了 memset 清零操作后,果然触发了几个死机,并且牵出来 【链表未初始化】 就直接使用的 BUG 操作。
- 有些操作可能运行一次并未发生死机等【当场就出现】的死机等错误,但是多次运行,错误累积起来,就会触发更严重的错误
- 代码编写时,规范化、充分测试非常有必要。
- 调试改善代码的时间往往比编写代码时间多很多,因此在代码设计编写时要多花点心思与时间,充分验证,减少后续问题的排查调试时间,提高工作效率。
小结
- 复杂点的数据结构,使用 动态内存申请后,建议直接 清零操作,以免默认的数据不是自己想要的,造成程序运行异常或者死机,简单的数据结构,如果能保证后面的操作把每个成员都赋值上,可以不清零
- 初始化、清零,这些简单的操作,也需要重视起来,以免耗费大量的软件排查与调试时间