C:嵌套的Ifs或Gotos

管理C程序资源的最佳方法是什么? 我应该使用嵌套的if结构还是应该使用goto语句?

我知道goto语句有很多禁忌 。 但是,我认为当地资源清理是合理的。 我提供了两个样本。 一个比较嵌套的if结构,另一个使用goto语句。 我个人发现goto语句使代码更容易阅读。 对于那些可能认为嵌套if提示更好的结构的人来说,想象一下如果数据类型不是char *,就像Windows句柄一样。 我觉得嵌套的if结构会随着一系列CreateFile函数或任何其他需要大量参数的函数而失控。

本文演示了本地goto语句为C代码创建RAII。 代码很简洁,很容易理解。 想象一下,作为一系列嵌套的if语句。

我知道goto在许多其他语言中都是禁忌,因为它们存在其他控制机制,如try / catch等,但是,在C中似乎是合适的。

#include  #define STRING_MAX 10 void gotoExample() { char *string1, *string2, *string3, *string4, *string5; if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto gotoExample_string1; if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto gotoExample_string2; if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto gotoExample_string3; if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto gotoExample_string4; if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto gotoExample_string5; //important code goes here gotoExample_string5: free(string4); gotoExample_string4: free(string3); gotoExample_string3: free(string2); gotoExample_string2: free(string1); gotoExample_string1: } void nestedIfExample() { char *string1, *string2, *string3, *string4, *string5; if (string1 = (char*) calloc(STRING_MAX, sizeof(char))) { if (string2 = (char*) calloc(STRING_MAX, sizeof(char))) { if (string3 = (char*) calloc(STRING_MAX, sizeof(char))) { if (string4 = (char*) calloc(STRING_MAX, sizeof(char))) { if (string5 = (char*) calloc(STRING_MAX, sizeof(char))) { //important code here free(string5); } free(string4); } free(string3); } free(string2); } free(string1); } } int main(int argc, char* argv[]) { nestedIfExample(); gotoExample(); return 0; } 

我还想引用Linus Torvalds关于Linux 内核中的 goto语句。

有时结构很糟糕 ,并且妨碍了使用“goto”更加清晰。

例如,有条件的东西是非常常见的。

在这种情况下,您有两种可能性

  • 使用goto,并开心,因为它不强制嵌套

    这使得代码更具可读性,因为代码只是按照算法所说的那样做。

  • 复制代码,并以嵌套的forms重写它,以便您可以
    使用结构化的跳跃。

    这通常会使代码更难以阅读,更难维护和更大。

Pascal语言是后一个问题的一个主要例子。 因为它没有“break”语句,所以(传统的)Pascal中的循环通常看起来像总屎,因为你必须添加完全任意的逻辑来说“我现在已经完成”。

是否可以接受资源管理? 我应该使用嵌套的if语句还是有更好的方法?

更新: C中Good Gotos的例子

使用goto进行清理的优点是它不易出错。 必须释放在每个返回点上分配的每个资源可能导致某人在进行维护工作时有一天会遗漏一些清理工作。

也就是说,我将引用Knuth的“带有goto语句的结构化编程”:

我主张在某些情况下消除go to,以及在其他情况下引入。

和克鲁斯在同一篇论文中对Dijkstra的引用:

“请不要陷入这样的陷阱,即我相信我对于声明的说法非常不合理。我有一种不舒服的感觉,就是其他人正在制造宗教信仰,好像编程的概念问题可以通过一个简单的编码规则forms的单一技巧!“ [29]。

毫无疑问,Dijkstra在编程世界中是一个令人敬畏的人物。 他的Goto被认为是有害的论文被夸大了。 是GoTo可能会被滥用,并且可能有害,但许多人认为对GoTo的彻底禁令是不合理的。 Knuth向Dijkstra提供了一个非常合理的反驳: GO TOs的结构化编程

阅读Knuth的论文,你会发现你的GoTo模式是GoTo的一个很好的用途。

顺便说一句,Dijkstra也很适合其他一些事情。 怎么样:

  • 面向对象编程是一个非常糟糕的想法,它只能起源于加利福尼亚。

Dijkstra是一位伟大的数学家,为计算机科学做出了巨大贡献。 但是,我不认为他必须处理或感兴趣的是我们99.99%的程序所做的日常类型的事情。

仅在原因和结构上使用GoTo。 很少使用它们。 但要使用它们。

如果使用goto你可以避免编写复杂的代码,然后使用goto

你的例子也可以这样写(没有goto ):

 void anotherExample() { char *string1, *string2, *string3, *string4, *string5; string1 = string2 = string3 = string4 = string5 = 0; if ((string1 = (char*) calloc(STRING_MAX, sizeof(char))) && (string2 = (char*) calloc(STRING_MAX, sizeof(char))) && (string3 = (char*) calloc(STRING_MAX, sizeof(char))) && (string4 = (char*) calloc(STRING_MAX, sizeof(char))) && (string5 = (char*) calloc(STRING_MAX, sizeof(char)))) { //important code here } free(string1); free(string2); free(string3); free(string4); free(string5); } 

总是在每个程序中使用一个goto来惹恼纯粹主义者


那是我的理念。

说真的,在某些情况下goto是合理的,特别是如果它只是做一些明显的事情,比如跳转到函数底部的公共返回码。

就个人而言,我过去曾以这种方式使用过goto。 人们讨厌它,因为它让人想起他们用来编写/维护的意大利面条代码,或者因为编写/维护这些代码的人击败了这些代码是邪恶的概念。

你肯定可以在没有goto的情况下写出一些像样的东西。 但在这种情况下,它不会造成任何伤害。

从你的两个选择中,goto自然更好,更好。 但是还有第三种更好的选择:使用递归!

我的代码结构不同于任何一个。 除非我有其他原因要做,否则我可能会写代码如下:

 char *strings[5] = {NULL}; int all_good = 1; for (i=0; i<5 && all_good; i++) { strings[i] = malloc(STRING_MAX); all_good &= strings[i] != NULL; } if (all_good) important_code(); for (int i=0; i<5; i++) free(strings[i]); 

您链接到的文章中的示例与您发布的代码之间的一个非常大的区别是,您的gotos标签是_并且它们的goto标签是cleanup_

您接下来正在使用goto line_1324 ,代码将被编辑,因此line_1234标签位于第47823行……

像示例一样使用它,并且要非常小心地编写要读取的代码。

如果你知道你正在做什么,结果代码看起来更干净,更可读(我敢打赌),那么使用goto绝对没有问题。 特别是您展示的“优雅的初始化故障恢复”示例被广泛使用。

顺便说一句,在编写初始化100个内容的结构化代码时,你需要100个级别的缩进…这很简单。

在C中, goto通常是近似清理代码的唯一方法,如C ++析构函数或Java finally子句。 因为它真的是你为此目的所拥有的最佳工具,所以我说要用它。 是的,这很容易被滥用,但是很多编程结构也是如此。 例如,大多数Java程序员会毫不犹豫地抛出exception,但如果将exception用于除错误报告之外的其他内容(例如流控制),则exception也容易被滥用。 但是如果你明确地使用goto来避免清理代码重复,那么可以肯定地说你可能没有滥用它。

例如,您可以在Linux内核中找到许多完美合理的goto用法。

对我来说,我更喜欢这种gotoerror handling方式。 将Nick D的片段更进一步,它使用一个通用的goto标签进行error handling。

 void gotoExample() { char *string1, *string2, *string3, *string4, *string5; string1 = string2 = string3 = string4 = string5 = NULL; if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto HANDLE_ERROR; if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto HANDLE_ERROR; if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto HANDLE_ERROR; if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto HANDLE_ERROR; if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) ) goto HANDLE_ERROR; //important code goes here HANDLE_ERROR: if (string5) free(string5); if (string4) free(string4); if (string3) free(string3); if (string2) free(string2); if (string1) free(string1); }