我一直有一个印象,如果可能的话,永远不要使用goto。

然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。

在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。


当前回答

这些年来,我写了不少汇编语言。最终,每一种高级语言都被编译成gotos。好吧,叫它们“分支”或“跳跃”或其他什么,但它们是gotos。有人能写无goto汇编器吗?

当然,你可以向Fortran、C或BASIC程序员指出,gotos的泛滥就像意大利肉酱面一样。然而,答案不是避免它们,而是小心地使用它们。

刀可以用来准备食物,解救某人,或者杀死某人。我们会因为害怕后者而没有刀吗?同样,“后向”:不小心使用它会碍事,小心使用它会有所帮助。

其他回答

goto不好的一个原因是,除了编码风格之外,你可以用它来创建重叠但非嵌套的循环:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

这将创建一个奇怪的,但可能是合法的流控制结构,其中可能有(a, b, c, b, a, b, a, b, b,…)这样的序列,这让编译器黑客不高兴。显然,有许多聪明的优化技巧依赖于这种类型的结构不发生。(我应该检查一下我的龙书……)这样做的结果(使用一些编译器)可能是对包含gotos的代码没有进行其他优化。

如果你知道它只是“哦,顺便说一下”,恰好说服编译器发出更快的代码,那么它可能会很有用。就我个人而言,我更喜欢在使用像goto这样的技巧之前尝试向编译器解释什么是可能的,什么是不可能的,但可以说,我也可能在破解汇编程序之前尝试goto。

嗯,有一件事总是比goto更糟糕;奇怪地使用其他程序流操作符来避免goto:

例子:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

在Perl模块中,有时希望动态地创建子例程或闭包。问题是,一旦你创建了子例程,你如何得到它。你可以直接调用它,但是如果子例程使用caller(),那么它就没有那么有用了。这就是goto &子例程变化可能有用的地方。

这里有一个简单的例子:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

您还可以使用这种形式的goto来提供尾部调用优化的基本形式。

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

(在Perl 5 version 16中,最好写成goto __SUB__;)

有一个模块会导入尾修饰符,如果你不喜欢使用这种形式的goto,还有一个模块会导入递归。

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

使用goto的大多数其他原因都可以用其他关键字更好地完成。

比如重写一段代码:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

或者从多个地方找到最后一段代码:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

如果有,为什么?

C语言没有多级/标记的中断,并不是所有的控制流都可以用C语言的迭代和决策原语轻松建模。Gotos对纠正这些缺陷大有帮助。

有时使用某种类型的标志变量来实现一种伪多级中断更清晰,但它并不总是优于goto(至少goto可以轻松地确定控制的位置,不像标志变量),有时您只是不想为了避免goto而付出旗帜/其他扭曲的性能代价。

Libavcodec是一段性能敏感的代码。控制流的直接表达可能是优先考虑的,因为它往往会运行得更好。

我在以下情况下使用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;     
}

当您需要更改释放语句中的某些内容时(每个语句在代码中使用一次),第二个版本使它变得更容易,并减少了在添加新分支时跳过其中任何一个语句的机会。在函数中移动它们在这里不会有帮助,因为可以在不同的“级别”进行释放。