C语言C语言必知必会

【记录一次 Debug】圆括号位置放错导致的 bug

2020-05-07  本文已影响0人  不会编程的程序圆

码字不易,对你有帮助 点赞/转发/关注 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新

推荐阅读原文:
https://mp.weixin.qq.com/s/c5jot1YJcyeIniFkTVdNag

请看下面的程序,它用来进行复制文件的操作,你觉得它有问题吗:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char* argv[]) {

    FILE* src_fp, * dest_fp;
    int ch;

    if (argc != 3) {
        fprintf(stderr, "usage: fcopy source dest\n");
        exit(EXIT_FAILURE);
    }

    if ((src_fp = fopen(argv[1], "rb") == NULL)) {
        fprintf(stderr, "Can't open file %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((dest_fp = fopen(argv[2], "wb")) == NULL) {
        fprintf(stderr, "Can't open file %s\n", argv[2]);
        fclose(src_fp);
        exit(EXIT_FAILURE);
    }

    while ((ch = getc(src_fp)) != EOF)
        putc(ch, dest_fp);

    fclose(src_fp);
    fclose(dest_fp);

    return 0;
}

程序可以正常编译,但是当我们在命令行中输入正确的命令(命令格式:可执行程序名 文件1 文件2)想要复制文件时,会出现一个 assertion :

image

遇到这种问题我们不要慌张。我们先阅读一下这个错误提示,看能否找到可以帮助自己 debug 的有用信息。

第一次看这个错误提示我是摸不着头脑的。我仔细的检查了自己的程序,在确定“没有问题”后,我又仔细的看了看这个错误提示。请大家注意 fgetc.cpp 这里,这说明出错的地方有可能是 fgetc 函数内部。

注意接下来的一行:Expression: stream.valid();

到目前为止,我们知道可能是是程序执行到 fgetc 函数内部中 stream.valid() 这一行时产生了错误。那么问题到底是什么呢?

第一种办法是 google 一下,看看有没有类似的问题解答;如果没有,那么你就要靠自己了。

我们思考一下,表达式 stream.valid() 什么情况下会出错?有人可能要说了,我连这个表达式的意思是什么都不知道,我怎么知道什么情况下会出错呢?其实我也不知道,但这不影响我们思考出错的可能性。

最直观的一种可能就是 stream 是空指针时,对其进行成员访问。现在我们需要做的就是找到自己程序中哪里可能传入了空指针。

这也很简单,程序中我们只有一个函数和 fgetc 有关 —— getc 。但为什么我们用的是 getc 但是出错的是 fgetc 呢?因为 getc 函数的实现依赖于 fgetc 函数。

我们来看一下我们对 getc 的调用:

getc(src_fp)

这时你可能又要说了,src_fp 我们不是做过空指针检查吗,为什么还会出错?不要着急着下结论,我们来看看这个检查 src_fp 的语句:

    if ((src_fp = fopen(argv[1], "rb") == NULL)) {
        ...
    }

其实到这一步,明眼人都能看出问题的所在了。

当我确定了 src_fp 可能为空指针时,又看到了 if 语句中的 = 和 == ,我已经明白错误所在了。读过《C 陷阱和缺陷》的朋友都清楚,这是一个经典的由优先级引发的错误:

因为 C 语言中,赋值运算的优先级往往是比大多数其他运算符都要低的(包括关系运算符 == 和 != ),if 语句的判断会被编译器理解为:

if ( (src_fp =  (fopen(argv[1], "rb") == NULL) ) {
        ...
    }

我们知道:fopen(argv[1], "rb") == NULL的值的可能只有两个:0 或 1,不管我们将哪个赋值给 src_fp ,都会引发问题。

虽然最终的错误严谨的说并不能说是空指针异常,但是我们发现了形参 stream 是有问题的,也就说明我们传入的实参是不正确的。

引发这个问题的原因是这样的:在我写这个 if 语句的判断表达式时,我在 if 后打了一次左括号,VS 为我补了一个右括号(这样就完整的录入了())。然后在括号内我先写了 src_fp = fopen(argv[1], "rb")。在要写出判等语句前,我认为应该为前面的赋值语句添加括号,所以我将光标移动到了 src_fp 前,打出了左括号(,然后又我将光标移动到 fopen 函数的调用后,敲了一次左括号,很可惜,VS 就是这么坑爹,由于右边已经有了一个右括号(这是属于整体的),我白敲了一次右括号,并没有录入这个右括号。后面在写出判等代码后(if ((src_fp = fopen(argv[1], "rb") == NULL)),VS 其实是有报错的,但我自以为是的只是简单的在后面添加了一个右括号而已。从而造成了这个 bug 。

因为一个不经大脑的右括号,我花了很久 debug ,又“浪费”时间写了一篇博客!这个故事告诉我们,下次在你添加右括号时,一定要停下了思考一下,以避免写一篇“不必要”的博客。

虽然这个 bug 其实并不难找,但是我依然明白了,即使面对可能再难的 bug,稍加思考,它可能就会变成“纸老虎”。

或者,让你自己不要写出这样的 bug 。

上一篇下一篇

猜你喜欢

热点阅读