C/C++ goto伤了我的心

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/diangangqin/article/details/100584338

C/C++程序员或者初学C/C++都会遇到goto的问题,是否该使用goto,一派是坚决抵制,坚决不用,goto使程序难以分析,并且用code证明任何goto都是不必要的,都可以进行合理替换;另一派坚持goto使程序更简洁,能够很好的统一处理程序的异常情况。针对哪种语言是最好的,以及语言中的特定性质,我从来没有倾向性,不会选择战队。每种语言都有自己适用的情形,每种性质都有存在的合理性。goto就像一把切菜刀,用好了,锋利无比,能够切肉切菜,做出美味;用不好,可能伤到自己,甚至伤到别人。所以想用好菜刀必须要小心,提高警惕,不能麻痹大意。

我自己写的code和项目中遗留的code,使用goto的情形都是程序遇到异常情况,需要提前退出,统一做error handling,释放内存等。但是如何小心,还是出了错。

简单描述一下code的逻辑,code是分层的,我负责的层属于中间层,中间层下面是底层,和硬件打交道,中间层和硬件的交互都是通过底层实现的,中间层和底层有一套比较标准的接口,中间层传入底层的参数是什么,底层应该返回中间层什么结果,事前都有明确的描述,当然中间层针对底层的返回结果需要做一些保护,因为底层可能返回一些错误结果,可能是合理情形。

sample code

int testSample(void) {
// Line是一个类,底层的返回结果类型就是Line,Line中有保存数据的元素data和有解析读取返回结果的各种函数。
    Line *line = NULL;
    char *response = NULL;
// 调用底层函数 callLowLayer 约定的返回结果的data数据应该是用4个逗号分隔开的5个数据,Line->data进行保存
    line = callLowLayer(para1, para2);
// 之后读取line->data的五个数据,如果有异常,就goto error; 异常包括不足5个数据,或者数据类型不对
//读取第一个,第二个,第三个数据,如果发生error,goto error
    if (read(line->data first/second/third data)) {
        goto error;
    }
    if(response = read(line->data forth data)) {
        goto error;
    }
    if( read(line->data fifth data)) {
        goto error;
    }
    response = asciiToUnicode(response); ***// 之后将数据转成Unicode数据,asciiToUnicode会使用malloc分配新的memory***
    if (handle response error) {
        got to error;
    }
    if (response != NULL) {
        free(response);
        response = NULL;
    }
    return;
error:
    error handling;
    if (response != NULL) {
        free(response);
        response = NULL;
    }
}

上面的这种goto的使用方法还是比较常见的,程序中出现异常后,统一回到error的部分进行统一处理。遇到的问题是底层返回的数据只有四个,不是五个,导致程序直接挂了,这个问题在内部不能复现,只有客户能复现问题,由于没有call stack,只有日志,只是看到调用了callLowLayer后程序直接异常了,并且callLowLayer的返回结果数据已经被打印,确实只有四个数据,不是五个,所以转给底层分析,底层分析后发现data数据中含有break line,直接换行了,所以data只有前面四个,有一个被忽略掉了,虽然问题分析厘清了,但是如果底层进行修改比较困难,因为底层需要检查每个string是否含有break line,之后返回特定error给中间层,这个非常复杂并浪费时间。所以底层建议上面进行修改,问题又回到中间层,中间层查看code后,认为程序没有死到自己的逻辑里面,并且自己的逻辑也就是testSample已经做了error handling,问题应该出在的read的部分,是由read引起的问题,可能是遇到这种情形读取fail引起的问题,所以找到Line负责的owner进行分析,Line厘清问题原因后,认为应该是底层改,因为没有call stack,Line也确实怀疑可能自己的逻辑出了问题,因为Line从来没有出过这种问题,也没有处理过这种异常情形,问题又回到了底层。这时候圣旨来了,客户要求问题改在中间层,或者更上层,只要能够处理这种error,不影响后面程序的运行就可以,不要改在底层,因为底层的改动可能影响面比较大,造成不预期的问题,另外一个原因是问题的确是出在AP层,AP应该能够处理这种特殊error,保证程序不死掉,底层直接把问题踢回了中间层。这个问题经过几轮的讨论已经花费了很长时间,后面虽然抓到了call stack,但是展示的信息没有那么充分,只能看到testSample出现了问题,memory free出现了问题,直接abort了。

问题讨论久了,可能已经不关注问题的本身,有些钻牛角尖了,就是认为自己是对的,要求对方进行修改,而每个人都有这样的想法,问题比较难推动了,而Line的owner就是认为应该是底层改才对。但是问题是现在要求上层改了。我已经没有退路了,现在球在我的脚下,我必须踢出去,这个时候我的想法还是我的逻辑没有问题,我要证明是Line的问题就可以了,把球踢给他,我把球传出去就可以了。这个时间段我虽然又看了code,但是仍然没有发现问题,甚至没有怀疑过这段逻辑存在的问题,感觉自己已经被一种莫名的东西蒙蔽了双眼和心智,异或是自己的code就是认为没有问题,而且code跑了很长时间从来没有出过问题更增添了那一份信心。

我接下来就是模拟callLowLayer返回的结果了,证明是Line的问题,直接构建了一个Line,替换掉callLowLayer,运行程序,的确出现了同样的问题,这样就简单了,加了大量的log,最后goto error的部分也都添加了。再次运行程序发现程序异常,但是log居然打印到error start了,即error:的第一行log。当时看到start后简直是浇了一盆凉水,怀里抱着冰,掉入了谷底呀,怎么Line没有问题,为什么只有start呢?各种疑问出现在大脑里,迷乱而空洞洞。我只好默默地去了厕所,冷静冷静,因为我好想静静。果然我静下来了,重新梳理了问题和log,还有code,发现了问题点,double free response了,free response只有正常流程的时候才需要,如果读取数据出现异常走不到asciiToUnicode,也就重新分配不了内存,也不需要进行free,虽然error:free的时候判断了response是否是NULL,但是这时候不是NULL,因为已经读取到了第四个数据,已经有值了,读取第五个数据的时候,因为没有第五个,直接就goto error了。也就是打印了log(“3”);goto error后直接free了response,但是这时候response是指向的line->data的末尾前一个数据位置,free之后,Line的析构函数还会被调用,会再次free line->data,造成了double free。问题查找到了,不是特别高兴,而是有些惭愧,不好意思了,由于自己的疏忽,问题这么长时间没有解决,有一些惭愧。修改就简单了,直接把error后面的free response删除掉了。之后复测问题没有再发生,release正式patch给客户,客户实际环境也没有复现问题。这题就到此结束了。

虽然问题解决了,当我复盘整个问题的时候,感觉伤痕累累,问题其实可以尽早的解决,耽误了这么长时间。自己身上已经没有刚毕业的那份朝气和兴奋,对问题不断的追求,而是能够把问题推出去就推出去,不是自己的问题,不愿意多看一分钟。学会了嘴舌之争,而不是切实的分析问题,不足够冷静。异或是
自满自足了。

sample code

int testSample(void) {
// Line是一个类,底层的返回结果类型就是Line,Line中有解析读取返回结果的各种函数。
    Line *line = NULL;
    char *response = NULL;
// 调用底层函数 callLowLayer 约定的返回结果的data数据应该是用逗号分隔开的5个数据,Line->data进行保存
    line = callLowLayer(para1, para2);
// 之后读取line->data的五个数据,如果有异常goto error; 异常包括不足5个数据,或者数据类型不对
//读取第一个,第二个,第三个数据,如果发生error,goto error
    if (read(line->data first/second/third data)) {
        log("1");
        goto error;
    }
    if(response = read(line->data forth data)) {
        log("2");
        goto error;
    }
    if( read(line->data fifth data)) {
        log("3");
        goto error;
    }
    response = asciiToUnicode(response); // 之后将数据转成Unicode数据,asciiToUnicode会使用malloc分配新的memory
    if (handle response error) {
        log("4");
        got to error;
    }
    if (response != NULL) {
        free(response);
        response = NULL;
    }
    log("5");
    return;
error:
    // 后面复现问题添加了
    log("start");
    error handling;
    if (response != NULL) {
        free(response);
        response = NULL;
    }
    //后面复现问题后添加的
    log("end");
}

终于将这个过程记录下来,语言从来没有那么复杂,复杂的是人。goto error虽然简单,也请小心使用,切莫大意,不然伤了自己,还会伤到自己的心,伤了他人的心。

猜你喜欢

转载自blog.csdn.net/diangangqin/article/details/100584338