排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃

 最近在使用duilib开源库实现图片查看工具软件ImageViewer,调试时发现,程序刚启动时就访问了0xcdcdcdcd内存地址,触发内存访问违例,导致了软件崩溃。本文分享一下这一问题的排查过程。

1、问题描述

       使用Visual Studio对ImageViewer程序进行Debug下的调试,结果还没弹出程序的主窗口,就报出了内存访问违例的提示,提示框如下:

图片[1]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

提示在访问0xcdcdcdcd内存时产生了内存访问违例。以前我们多次讲过0xcccccccc、0xcdcdcdcd和0xfeeefeee这几个常见的特殊值,如下所示:

图片[2]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

即本例中0xcdcdcdcd含义是:程序在Debug下运行时,Debug版本微软C++运行时库会将未初始化的堆内存中的内容都初始化为0xcdcdcdcd。所以在Debug下调试代码遇到这样的值,可能是因为申请的堆内存没有初始化引起的。

       点击提示框中继续按钮,代码会中断在如下的代码处:

图片[3]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

即异常发生在在m_pCaption->GetPos这句代码上。此时,查看了一下指针变量m_pCaption内存中的值,就是0xcdcdcdcd,可能是m_pCaption等控件的指针变量没有初始化引起的。此时查看函数的调用堆栈:

图片[4]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

函数是执行到CImageViewerWnd::HandMessage函数中触发上述调函数调用的,具体是在处理uMsg==WM_SIZE && wParam==SIZE_RESTORED分支代码中,如下:

图片[5]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

CImageViewerWnd就是ImageViewer程序的主窗口类。除了m_pCaption控件变量的值为0xcdcdcdcd,其他标题栏中的控件指针变量m_pBtnMax的值也是0xcdcdcdcd。接下来我们以m_pBtnMax值为0xcdcdcdcd为线索继续分析。

2、WM_SIZE(wParam:SIZE_RESTORED)消息与窗口Init接口调用的时序问题

       m_pCaption和m_pBtnMax等控件指针变量都是在CImageViewerWnd::Init函数中初始化获取到值的,如下所示:

图片[6]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

而上面异常发生在运行到uMsg==WM_SIZE && wParam==SIZE_RESTORED代码分支时,运行到该处代码,这些控件指针变量是没有初始化的,所以可以判定,在执行到此处的代码之前还没执行到CImageViewerWnd::Init函数。

       那CImageViewerWnd::Init函数是在什么被调用的呢?这个要看duilib开源库中框架的代码,框架中会调用系统API函数CreateWindow(Ex)去创建CImageViewerWnd窗口,API函数CreateWindow(Ex)会产生WM_CREATE消息,dui框架在收到WM_CREATE消息时就会去自动调用CImageViewerWnd::Init函数。

       所以,这说明什么呢?说明WM_SIZE消息会先于WM_CREATE消息产生,即初始化控件指针变量的函数CImageViewerWnd::Init还没执行时,就先收到了WM_SIZE消息,进入了WM_SIZE消息处理分支,就访问了未初始化的指针变量。

这显然和我们原先的认知是不一样的,我们原先一度认为,创建窗口时产生的消息WM_CREATE会先产生了,等窗口创建出来后,才会收到WM_SIZE消息。此案例让我们认识到,窗口的WM_SZIE会先于WM_CREATE消息产生

3、解决办法

       我们可以在CImageViewerWnd::HandMessage函数处理WM_SZIE消息的分支中添加处理,先判断m_pBtnMax指针是否为空?如下所示:

图片[7]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

这样显然是不行的,本例中正是访问到了未初始化的m_pCaption控件指针变量(变量值是运行时库在初始化堆内存时设置的默认值0xcdcdcdcd),0xcdcdcdcd也是不等于NULL的,所以仅仅添加控件指针变量是否为空的判断是不够的。 

       其实我们以前说过,我们在定义好变量之后,应该在最开始的时候就对变量进行初始化,即在C++类的构造函数中进行初始化的,即将所有控件指针变量都初始化为NULL,如下所示:

图片[8]-排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃-卡核

       这样我们在CImageViewerWnd::HandMessage函数处理WM_SZIE消息的分支中,判断控件指针变量是否为NULL,就没问题了,就不会再有崩溃了。

       最后,我们要强调一下,无论是函数中的局部变量,还是C++类中成员变量,都要进行初始化,要养成变量初始化的习惯。

© 版权声明
THE END
喜欢就支持一下吧
点赞3201赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容