我一直有一个印象,如果可能的话,永远不要使用goto。
然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。
在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。
我一直有一个印象,如果可能的话,永远不要使用goto。
然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。
在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。
当前回答
我在以下情况下使用goto: 当需要从不同位置的函数返回时,并且在返回之前需要进行一些初始化:
non-goto版本:
int doSomething (struct my_complicated_stuff *ctx)
{
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
db_disconnect(conn);
return -1;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
free(temp_data);
db_disconnect(conn);
return -2;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -3;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -4;
}
if (ctx->something_else->additional_check) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -5;
}
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return 0;
}
goto版本:
int doSomething_goto (struct my_complicated_stuff *ctx)
{
int ret=0;
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
ret=-1;
goto exit_db;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
ret=-2;
goto exit_freetmp;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
ret=-3;
goto exit;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
ret=-4;
goto exit_freekey;
}
if (ctx->something_else->additional_check) {
ret=-5;
goto exit_freekey;
}
exit_freekey:
rsa_free(key);
exit:
pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
free(temp_data);
exit_db:
db_disconnect(conn);
return ret;
}
当您需要更改释放语句中的某些内容时(每个语句在代码中使用一次),第二个版本使它变得更容易,并减少了在添加新分支时跳过其中任何一个语句的机会。在函数中移动它们在这里不会有帮助,因为可以在不同的“级别”进行释放。
其他回答
在c#中,switch语句不允许切换。因此,goto用于将控制转移到特定的开关箱标签或默认标签。
例如:
switch(value)
{
case 0:
Console.WriteLine("In case 0");
goto case 1;
case 1:
Console.WriteLine("In case 1");
goto case 2;
case 2:
Console.WriteLine("In case 2");
goto default;
default:
Console.WriteLine("In default");
break;
}
编辑:有一个例外的“不掉落”规则。如果case语句没有代码,则允许延迟执行。
如果有,为什么?
C语言没有多级/标记的中断,并不是所有的控制流都可以用C语言的迭代和决策原语轻松建模。Gotos对纠正这些缺陷大有帮助。
有时使用某种类型的标志变量来实现一种伪多级中断更清晰,但它并不总是优于goto(至少goto可以轻松地确定控制的位置,不像标志变量),有时您只是不想为了避免goto而付出旗帜/其他扭曲的性能代价。
Libavcodec是一段性能敏感的代码。控制流的直接表达可能是优先考虑的,因为它往往会运行得更好。
我们使用的goto规则是,goto可以跳转到函数中的单个退出清理点。在真正复杂的函数中,我们放松了这个规则,允许其他跳转。在这两种情况下,我们都避免了经常在错误代码检查中出现的深度嵌套的if语句,这有助于可读性和维护。
“goto”的问题和“无goto编程”运动最重要的论点是,如果你过于频繁地使用它,尽管你的代码可能表现正确,但它会变得不可读、不可维护、不可审查等。在99.99%的情况下,“goto”会导致意大利面条代码。就我个人而言,我想不出任何好的理由来解释为什么我会使用“goto”。
计算机科学家Edsger Dijkstra在该领域做出了重大贡献,他也因批评GoTo的使用而闻名。 维基百科上有一篇关于他观点的短文。