天天看点

一起空指针引发的程序问题的排查过程

      【文章摘要】

       在C程序中,指针操作是难点和精华所在。指针一旦使用不当,极有可能造成程序的崩溃。

       本文对一空指针引发的程序问题的排查过程进行了详细的介绍,为相关软件问题的分析及解决提供了有益的参考。

        一、问题描述

       最近,某程序在测试过程中突然崩溃。日志中出现如下内容:

#0  0xf64f2b3a in FunctionA(event=666,dlgindex=0, ucErrNo=1 '\001') at src/A.c:6838
       #1  0xf64e3a4f in FunctionB(in=0x80efd4d"") at src/A.c:2781
       #2  0xf64dba3b in FunctionC(in=0x80efd4d"", out=0x0, dataPtr=0x0) at src/A.c:303      

        二、日志内容分析

        通过以上日志文件内容,我们可以看出如下信息:

        第一,程序最终是在FunctionA函数中崩溃的,该函数有三个入参:event、dlgindex和ucErrNo。其中,出问题时的event为666,dlgindex为0,ucErrNo为1。

        第二,调用FunctionA的是FunctionB函数,该函数只有一个入参:in。

        三、问题的排查过程

       通过对日志内容的分析,我们查看了event为666的程序代码,主要用于从数据库中获取某几个系统参数的值。其整个执行流程如图1所示。

一起空指针引发的程序问题的排查过程

图1 程序执行流程

         通过图1,我们可以看到,程序问题出现在了数据库在设定的时间之内没有应答而进入超时处理的流程中。

         我们又仔细查看了日志信息,发现出问题的地方在“A.c”文件的第6838行。该行及前一行代码如下:

         pReq  = (T_RetInfoFromReqMsg*)DlgBuf[dlgindex].para;  (第6837行代码)

         flowid  = pReq->flowid;                               (第6838行代码)

        第6837行代码对pReq指针赋值,第6838行代码将pReq对应的结构体中的flowid变量赋给整型变量flowid。

DlgBuf[dlgindex].para”指针的值有问题。我们又仔细查看了程序代码,发现该指针是在向数据库发消息的函数中被赋值的。其代码如下:

UINT32 SendToDB(…, UINT8 *para, UINT32 paraLen)
{
    ……
    if ((para != NULL)&& (paraLen > 0))
    {
        memcpy(DlgBuf[iDlgIndex].para, para,(int)paraLen);
    }
    ……
}      

DlgBuf是在程序刚启动的时候被初始化的。我们又看了调用SendToDB函数的代码行,如下:

SendToDB(…, NULL, 0);

para指针的值为空(NULL),paraLen的值为0。根据SendToDB函数的代码流程,DlgBuf[iDlgIndex].para不会被赋值,那么它就会为空(NULL)。

DlgBuf[iDlgIndex].para指针为空(NULL),那么在处理数据库超时的流程中,因为序列号是同一个,则DlgBuf[dlgindex].para指针也为空(NULL),这也导致pReq指针为空。后面要用到该指针对应的结构体中的值,就会找不到,因此程序便崩溃了。

SendToDB函数的代码,不传空指针进去,如下:

T_RetInfoFromReqMsg tRetInfoFromReqMsg = {0};
……
SendToDB(…, &tRetInfoFromReqMsg, sizeof(T_RetInfoFromReqMsg));      

        对修改后的程序进行测试,就不会出现问题了。

        四、总结

        通过本次问题排查,我们总结出的经验有以下几个:

        (1) 对于指针的使用,一定要小心。对于重要的指针,一定要先检查其是否有准确的值之后再使用,避免使用空指针。

        (2) 不管是在哪个函数中,在使用指针之前,一定要先检查该指针是否为空(NULL)。这也算是对程序进行异常保护。