start
很多人不喜欢goto,认为goto为代码结构带来了很多灾难,但是用的好的话,其实goto同样能够优雅的解决很多问题,比如下面说的这个
这个算是最近看的代码之髓第六章的一个读书扩展了,对于C里面,如何更好的处理错误,其中提到了一点Linux内核编码风格里面提到可以用goto来更好的集中处理错误,减少冗余代码,详情可以看Linux内核编码风格中的第七章——集中处理函数的退出
怎么做
内核编码风格第七章
第七章原文大意是这么说的
尽管有的人不喜欢goto,但是呢其实编译器里面还是一直用着无条件跳转这一和goto等价的东西。在一个函数里面如果有多个地方都需要进行清理和提交任务的时候,goto的好处就显现出来了。
为什么呢?下面列了四条:
- 无条件跳转的语句,很容易的理解和跟踪
- 减少了嵌套
- 避免了修改代码后没有修改每一个退出点的清理代码而导致的错误
- 减轻了编译器去除冗余代码的优化工作
例子
简单写了个例子,打开三个文本文件,并把其中的内容合并到一个文件中去,如果中间出现了错误,那么就不进行合并
思路,我们使用一种笨拙的方式
- 顺序打开4个文件
- 如果其中一个出错,那么终止文件的打开并结束函数的处理并返回错误信息和代码,否则继续打开下一个文件并合并
下面我们用笨拙(主要是我比较笨拙)的写法和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
- 公众号主要发一些开发相关的技术文章
- 谈谈自己对技术的理解,经验
- 也许会谈谈人生的感悟
- 本人不是很高产,但是力求保证质量和原创