知识共享许可协议本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。本文仅作为个人学习记录使用,欢迎在许可协议范围内转载或使用,请尊重版权并且保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS方式订阅本站,这样您将能在第一时间获取本站信息。

start

很多人不喜欢goto,认为goto为代码结构带来了很多灾难,但是用的好的话,其实goto同样能够优雅的解决很多问题,比如下面说的这个

这个算是最近看的代码之髓第六章的一个读书扩展了,对于C里面,如何更好的处理错误,其中提到了一点Linux内核编码风格里面提到可以用goto来更好的集中处理错误,减少冗余代码,详情可以看Linux内核编码风格中的第七章——集中处理函数的退出

怎么做

内核编码风格第七章

第七章原文大意是这么说的

尽管有的人不喜欢goto,但是呢其实编译器里面还是一直用着无条件跳转这一和goto等价的东西。在一个函数里面如果有多个地方都需要进行清理和提交任务的时候,goto的好处就显现出来了。

为什么呢?下面列了四条:

  • 无条件跳转的语句,很容易的理解和跟踪
  • 减少了嵌套
  • 避免了修改代码后没有修改每一个退出点的清理代码而导致的错误
  • 减轻了编译器去除冗余代码的优化工作

例子

简单写了个例子,打开三个文本文件,并把其中的内容合并到一个文件中去,如果中间出现了错误,那么就不进行合并

思路,我们使用一种笨拙的方式

  1. 顺序打开4个文件
  2. 如果其中一个出错,那么终止文件的打开并结束函数的处理并返回错误信息和代码,否则继续打开下一个文件并合并

下面我们用笨拙(主要是我比较笨拙)的写法和goto优化的写法分别来完成这个函数,函数内容只包括了打开文件的部分代码,主要是觉得这部分代码足够示例了

常规的写法

int mergeFile(char *file1, char *file2, char *file3, char *outfile, char *errorMessage)
{
    FILE *fp1 = NULL;
    FILE *fp2 = NULL;
    FILE *fp3 = NULL;
    FILE *fpout = NULL;
    char *fileString = NULL;
    int filesize = 0;

    fp1 = fopen(file1, "r");
    if (!fp1) {
        strcpy(errorMessage, "file 1 open failed");
        return EXIT_FAILURE;
    }
    fp2 = fopen(file2, "r");
    if (!fp2) {
        fclose(fp1);
        strcpy(errorMessage, "file 2 open failed");
        return EXIT_FAILURE;
    }
    fp3 = fopen(file3, "r");
    if (!fp3) {
        fclose(fp1);
        fclose(fp2);
        strcpy(errorMessage, "file 3 open failed");
        return EXIT_FAILURE;
    }
    fpout = fopen(outfile, "w+");
    if (!fpout) {
        fclose(fp1);
        fclose(fp2);
        fclose(fp3);
        strcpy(errorMessage, "file out open failed");
        return EXIT_FAILURE;
    }
    filesize += sizeOfFile(fp1);
    filesize += sizeOfFile(fp2);
    filesize += sizeOfFile(fp3);
    fileString = (char *) malloc(sizeof(char) * filesize);
    if (!fileString) {
        fclose(fp1);
        fclose(fp2);
        fclose(fp3);
        fclose(fpout);
        strcpy(errorMessage, "alloc memory error");
        return EXIT_FAILURE;
    }

    // 读取文件内容
    // 写入文件

    fclose(fp1);
    fclose(fp2);
    fclose(fp3);
    fclose(fpout);
    free(fileString);
    strcpy(errorMessage, "everything is ok");
    return EXIT_SUCCESS;
}

相信大家在第一次处理多个文件的打开和释放操作的时候,一定都写过上面这样的代码,每个错误都处理了,但是似乎很麻烦,并且每个错误的位置都写上了,写demo都写的快吐了,真后悔说三个文件,应该说两个就好了-,-。。。并且上面的demo还没有吧合并文件的逻辑加入,比如文件读取失败,文件写入失败的情况也没有放进去,如果这些也加进去的话,方法会变得更加复杂

用goto优化一下

int mergeFile2(char *file1, char *file2, char *file3, char *outfile, char *errorMessage)
{
    int ret = EXIT_SUCCESS;
    FILE *fp1 = NULL;
    FILE *fp2 = NULL;
    FILE *fp3 = NULL;
    FILE *fpout = NULL;
    char *fileString = NULL;
    int filesize = 0;
    fp1 = fopen(file1, "r");
    if (!fp1) {
        strcpy(errorMessage, "file 1 open failed");
        ret = EXIT_FAILURE;
        goto EXIT_POINT;
    }
    fp2 = fopen(file2, "r");
    if (!fp2) {
        strcpy(errorMessage, "file 2 open failed");
        ret = EXIT_FAILURE;
        goto EXIT_POINT;
    }
    fp3 = fopen(file3, "r");
    if (!fp3) {
        strcpy(errorMessage, "file 3 open failed");
        ret = EXIT_FAILURE;
        goto EXIT_POINT;
    }
    fpout = fopen(outfile, "w+");
    if (!fpout) {
        strcpy(errorMessage, "file out open failed");
        ret = EXIT_FAILURE;
        goto EXIT_POINT;
    }
    filesize += sizeOfFile(fp1);
    filesize += sizeOfFile(fp2);
    filesize += sizeOfFile(fp3);
    fileString = (char *) malloc(sizeof(char) * filesize);
    if (!fileString) {
        strcpy(errorMessage, "alloc memory error");
        ret = EXIT_FAILURE;
        goto EXIT_POINT;
    }

    // 读取文件内容
    // 写入文件

    strcpy(errorMessage, "everything is ok");

EXIT_POINT:
    if (fp1) fclose(fp1);
    if (fp2) fclose(fp2);
    if (fp3) fclose(fp3);
    if (fpout) fclose(fpout);
    if (fileString) free(fileString);
    return ret;
}

一眼看上去,多了goto后,整个代码非常的简洁和一致,出错的地方只需要设置错误信息和错误代码即可,之后,变交给EXIT_POINT那里的代码来执行清理工作,从代码上来看至少满足了内核编码风格中前三条的描述,提高了代码的整体可读性

end

总结下,万恶的goto其实也并不是毒药,只要使用的得当,也是一个利器,当然对于这种情况的goto的使用,其实是因为C语言里面没有异常处理机制,进而提出的一种错误处理思路,对于有异常处理的语言大家都很习惯的使用try-catch-finally语句对代码中的错误进行处理并进行最后的清理工作,但是对于C语言来说,这样处理的风格简单总有简单的魅力

最后欢迎大家订阅我的微信公众号 Little Code

公众号

  • 公众号主要发一些开发相关的技术文章
  • 谈谈自己对技术的理解,经验
  • 也许会谈谈人生的感悟
  • 本人不是很高产,但是力求保证质量和原创