[英]C: Nested Ifs or Gotos
管理C程序资源的最佳方法是什么? 我应该使用嵌套的if结构还是应该使用goto语句?
我知道goto语句有很多禁忌 。 但是,我认为当地资源清理是合理的。 我提供了两个样品。 一个比较嵌套的if结构,另一个使用goto语句。 我个人发现goto语句使代码更容易阅读。 对于那些可能认为嵌套if提示更好的结构的人来说,想象一下如果数据类型不是char *,就像Windows句柄一样。 我觉得嵌套的if结构会随着一系列CreateFile函数或任何其他需要大量参数的函数而失控。
本文演示了本地goto语句为C代码创建RAII。 代码很简洁易于理解。 想象一下,作为一系列嵌套的if语句。
我知道goto在许多其他语言中都是禁忌,因为它们存在其他控制机制,如try / catch等,但是,在C中似乎是合适的。
#include <stdlib.h>
#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,并开心,因为它不强制嵌套
这使得代码更具可读性,因为代码只执行算法所说的应该做的事情。
复制代码,并以嵌套的形式重写它,以便您可以
使用结构化的跳跃。这通常会使代码更难以阅读,更难维护和更大。
Pascal语言是后一个问题的一个主要例子。 因为它没有“break”语句,所以(传统的)Pascal中的循环通常看起来像是完全糟糕,因为你必须添加完全任意的逻辑来说“我现在已经完成了”。
是转到资源管理是否可以接受? 我应该使用嵌套的if语句还是有更好的方法?
更新: C中Good Gotos的例子
如果使用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);
}
毫无疑问,Dijkstra在编程世界中是一个令人敬畏的人物。 他的Goto被认为是有害的论文被夸大了。 是GoTo可能被滥用,并且可能有害,但许多人认为对GoTo的彻底禁令是不合理的。 Knuth向Dijkstra提供了一个非常合理的反驳: GO TOs的结构化编程
阅读Knuth的论文,你会发现你的GoTo模式是GoTo的一个很好的用途。
顺便说一下,Dijkstra也很适合其他一些事情。 怎么样:
Dijkstra是一位伟大的数学家,为计算机科学做出了巨大贡献。 但是,我不认为他必须处理或感兴趣的是我们99.99%的程序所做的日常类型的事情。
仅在原因和结构上使用GoTo。 很少使用它们。 但要使用它们。
使用goto
进行清理的优点是它不易出错。 必须释放在每个返回点上分配的每个资源可能导致某人在进行维护工作时有一天会遗漏一些清理工作。
也就是说,我将引用Knuth的“带有goto语句的结构化编程”:
我主张在某些情况下消除go,以及在其他情况下引入。
和Knuth在同一篇论文中对Dijkstra的引用:
“请不要陷入这样的陷阱,即我相信我对于声明的说法非常不合理。我有一种不舒服的感觉,就是其他人正在制造宗教信仰,好像编程的概念问题可以通过一个简单的编码规则形式的单一技巧!“ [29]。
那是我的理念。
说真的,在某些情况下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标签是<functionName>_<number>
并且它们的goto标签是cleanup_<thing_to_cleanup>
。
您接下来正在使用goto line_1324
,代码将被编辑,因此line_1234
标签位于第47823行...
像示例一样使用它,并且要非常小心地编写要读取的代码。
如果你知道你正在做什么,结果代码看起来更干净,更可读(我敢打赌),那么使用goto绝对没有问题。 特别是您展示的“优雅的初始化故障恢复”示例被广泛使用。
顺便说一句,在编写初始化100个内容的结构化代码时,你需要100个级别的缩进...这很简单。
在C中, goto
通常是近似清理代码的唯一方法,如C ++析构函数或Java finally
子句。 因为它真的是你为此目的所拥有的最好的工具,所以我说使用它。 是的,它很容易滥用,但很多编程结构也是如此。 例如,大多数Java程序员会毫不犹豫地抛出异常,但如果将异常用于错误报告以外的其他操作(例如流控制),则异常也很容易被滥用。 但是如果你为了避免清理代码重复而明确地使用goto
,那么可以肯定地说你可能没有滥用它。
例如,您可以在Linux内核中找到许多完美合理的goto
用法。
对我来说,我更喜欢这种goto错误处理方式。 将Nick D的代码片段更进一步,它使用一个通用的goto标签进行错误处理。
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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.