管理 C 计划资源的最佳方法是什么?我应该使用嵌套的if结构还是应该使用goto语句?
我知道 goto 语句有很多禁忌。但是,我认为当地资源清理是合理的。我提供了两个样品。一个比较嵌套的if结构,另一个使用goto语句。我个人发现goto语句使代码更容易阅读。对于那些可能认为嵌套if 提示更好的结构的人来说,想象一下如果数据类型不是char *,就像Windows句柄一样。我觉得嵌套if 结构会因一系列 CreateFile 函数或任何其他需要大量参数的函数而失控。
这个article演示了本地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 Kernel内的 goto 语句。
有时结构是坏,并且 进入,并使用“转到” 更清楚。
例如,它很常见 有条件,没有NEST。
在这种情况下你有两个 可能性
使用goto,并且开心,因为它不强制嵌套
这使代码更多可读, 因为代码只是做了什么 算法说应该这样做。
复制代码,并以嵌套形式重写代码,以便您可以 使用结构化的跳跃。
这通常使代码更少 可读,难以维护,和 大。
Pascal语言就是一个很好的例子 后一个问题。因为它 没有“休息”声明, 循环(传统)Pascal结束 经常看起来像总屎,因为 你必须完全随意添加 说“我现在已经完成”的逻辑。
转到是否可以接受资源管理?我应该使用嵌套if 语句还是有更好的方法?
答案 0 :(得分:11)
如果使用goto
,您可以避免编写复杂的代码,那么请使用goto
。
您的示例也可以这样写(没有goto
s):
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);
}
答案 1 :(得分:11)
毫无疑问,Dijkstra在编程世界中是一个令人敬畏的人物。他的 Goto Considered Harmful论文 被夸大了。是GoTo可能会被滥用,并且可能有害,但许多人认为对GoTo的彻底禁令是 不保证。 Knuth向Dijkstra提供了一个非常合理的反驳: Structured Programming with GO TOs
阅读Knuth的论文,你会发现你的GoTo模式是一个很好的用途 对于GoTo。
顺便说一句,Dijkstra对其他一些东西也很有名。怎么样:Dijkstra是一位伟大的数学家,为计算机科学做出了巨大贡献。但是,我没有 认为他必须处理或感兴趣的日常类型的东西99.99% 我们的课程。
仅在原因和结构上使用GoTo。很少使用它们。但是要使用它们。
答案 2 :(得分:10)
使用goto
进行清理的优势在于它不易出错。必须释放在每个返回点上分配的每个资源,这可能导致某人在进行维护工作时有一天会遗漏一些清理工作。
那就是说,我将引用Knuth的“结构化编程与goto语句”:
我主张在某些情况下取消go,以及在其他情况下将其引入。
和Knuth在同一篇论文中对Dijkstra的引用:
“请不要陷入相信我对[发表声明]非常不妥当想象的陷阱。我有一种不舒服的感觉,就是其他人正在制造一种宗教信仰,好像编程的概念性问题可以通过一种简单的编码规则,通过一个技巧解决!“ [29]。
答案 3 :(得分:5)
这是我的理念。
说真的,在某些情况下goto
是合理的,特别是如果它只是做一些明显的事情,比如跳转到函数底部的常见返回代码。
答案 4 :(得分:3)
我个人过去曾以这种方式使用过goto。人们讨厌它,因为它让人想起他们用来编写/维护的意大利面条代码,或者因为编写/维护这些代码的人击败了那些带有邪恶的概念。
你肯定可以在没有goto的情况下写出一些体面的东西。但是在这种情况下它不会造成任何伤害。
答案 5 :(得分:2)
从你的两个选择中,goto自然更好,更好。但是还有第三种更好的选择:使用递归!
答案 6 :(得分:2)
我的代码结构不同于任何一个。除非我有其他原因,否则我可能会编写类似这样的代码:
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]);
答案 7 :(得分:2)
您链接到的文章中的示例与您发布的代码之间的一个非常大的区别是,您的gotos标签为<functionName>_<number>
,其goto标签为cleanup_<thing_to_cleanup>
。
您接下来正在使用goto line_1324
,代码将被编辑,因此line_1234
标签位于第47823行......
像示例一样使用它,并且要非常小心地编写要读取的代码。
答案 8 :(得分:1)
如果你知道自己在做什么,结果代码看起来更干净,更具可读性(我敢打赌),那么使用goto绝对没问题。特别是您展示的“优雅的初始化失败恢复”示例被广泛使用。
顺便说一句,在编写初始化100个内容的结构化代码时,你需要100个级别的缩进...这很简单。答案 9 :(得分:1)
在C中,goto
通常是近似清理代码的唯一方法,如C ++析构函数或Java finally
子句。因为它真的是你为此目的所拥有的最好的工具,所以我说使用它。是的,它很容易滥用,但很多编程结构也是如此。例如,大多数Java程序员会毫不犹豫地抛出异常,但如果将异常用于错误报告以外的其他操作(例如流控制),则异常也很容易被滥用。但是如果你明确地使用goto
来避免清理代码重复,那么可以肯定地说你可能没有滥用它。
例如,您可以在Linux内核中找到goto
的许多完全合理的用法。
答案 10 :(得分:0)
对我来说,我更喜欢这种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);
}