在支持循环和函数的语言中使用“goto”是否有利? 如果是这样,为什么?

我一直觉得,如果可能的话,不应该使用goto 。 在阅读libavcodec(用C编写)时,我注意到了它的多种用法。 在支持循环和函数的语言中使用goto是否有利? 如果是这样,为什么?

使用我意识到的“goto”语句有几个原因(有些已经说过了):

干净地退出function

通常在一个函数中,你可以分配资源,需要在多个地方退出。 程序员可以通过将资源清理代码放在函数的末尾来简化他们的代码,并且函数的所有“退出点”都会得到清理标签。 这样,您不必在函数的每个“退出点”编写清理代码。

退出嵌套循环

如果你在一个嵌套的循环中,并且需要跳出所有的循环,goto可以比break语句和if-checks更简单更简单。

低级别的性能改进

这只在perf-critical代码中有效,但goto语句执行速度非常快,可以在通过函数时提供提升。 然而,这是一把双刃剑,因为编译器通常不能优化包含gotos的代码。

请注意,在所有这些示例中,gotos都被限制在单个函数的范围内。

每个反转的人都直接或间接引用了Edsger Dijkstra的“ GoTo Considered Harmful article”来证实他们的立场。 糟糕的是,Dijkstra的文章几乎与目前使用goto语句的方式毫无关系,因此文章所说的对现代编程环境几乎没有适用性。 无信无义的米姆现在已经濒临一种宗教,直到它的经文从高位,高祭司和被认为是异端的(或更糟糕的)异教徒。

让我们把狄克斯特拉的论文放在背景中来阐明这个问题。

当狄克斯特拉写他的论文时,当时stream行的语言是BASIC,FORTRAN(早期的方言)和各种汇编语言等非结构化的程序语言。 使用高级语言的人们在扭曲的,扭曲的执行线程中跳过他们的代码库是非常普遍的,这就产生了“意大利面代码”这个术语。 你可以通过跳到迈克·菲尔德(Mike Mayfield)编写的经典迷航(Trek)游戏来看这个,试图弄清楚事情是如何运作的。 花点时间看看结束。

是迪杰斯特拉1968年在他的论文中所抨击的“肆无忌惮的用语”。 就是他生活的环境,导致他写这篇论文。 在你喜欢的任何地方,你可以随心所欲地跳到你喜欢的任何地方,这就是他正在批评和要求被阻止的事情。 与C或其他更现代的语言中的goto的贫弱的力量相比,这是相当可怕的。

我已经可以听到邪教徒们在面对异教徒时提出的高喊。 “但是,”他们会吟唱,“你可以使代码非常难以阅读与goto C.” 哦耶? 你可以使代码非常难以阅读,而无需goto 。 像这个:

 #define _ -F<00||--F-OO--; int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_ } 

没有一个在眼前,所以它必须很容易阅读,对吧? 或者这个怎​​么样:

 a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k, l)char* *l;{g= atoi(* ++l); for(k= 0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<= g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1; while(d <=g){ ++O;for (f=0;f< O&&d<=g ;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O &&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e ;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++ b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1)) <<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c] ;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5 |c]);} putchar( '\n');}} /*Mike Laman*/ 

也没有到那里。 因此它必须是可读的。

这些例子有什么意义? 这不是语言function,使不可读,不可维护的代码。 这不是语法。 这是不好的程序员。 不好的程序员,正如你在上面的项目中看到的,可以使任何语言function不可读和不可用。 就像那里的循环一样。 (你可以看到他们,对吧?)

现在公平地说,一些语言结构比其他语言更容易被滥用。 但是如果你是一个C程序员,那么在我开始讨论goto的十字军东征之前,我会更encryption切地关注#define大约50%的使用。

所以,对于那些想阅读的人来说,有几点要注意。

  1. Dijkstra关于goto语句的论文是为编程环境而编写的,在这种环境下, goto比大多数非汇编语言的现代语言具有更大的潜在破坏性。
  2. 自动抛弃goto所有用途,因为这样的说法有点理性,只能说“我曾试过玩过一次,但不喜欢,所以现在我反对”。
  3. 代码中现代(贫血)的goto语句的合法用法不能被其他构造所代替。
  4. 当然,这些陈述是非法使用的。
  5. 也有对现代控制语言的非法使用,比如说“憎恶”可憎的地方。 这些往往比明智地使用goto更糟糕。

请记住,当你在一个又一个的投票中投了我一个,在过去的15 – 20年里,我已经使用了我自己的(非汇编)代码。

我等待着愤怒的尖叫和-1张气喘吁吁的投票。

盲目服从最佳实践并不是最佳实践。 避免goto语句作为stream量控制的主要forms的想法是避免产生不可读的意大利面代码。 如果在适当的地方谨慎地使用它们,它们有时可能是expression一个想法的最简单,最清楚的方式。 Zortech C ++编译器和D编程语言的创build者Walter Bright经常使用它们,但要审慎。 即使使用goto语句,他的代码仍然是完全可读的。

底线:为了避免goto而避免goto是毫无意义的。 你真正想避免的是产生不可读的代码。 如果你的goto -laden代码是可读的,那么没有错。

由于goto关于程序stream程的推理很难1 (也就是“意大利面条代码”), goto一般只是用来补偿丢失的特征: goto的使用实际上是可以接受的,但只有当语言不提供更多的结构以获得相同的目标。 以怀疑的例子:

我们使用的goto规则是goto可以跳转到函数中的单个出口清理点。

这是真的 – 但只有当语言不允许使用清理代码(如RAII或finally )进行结构化的exception处理时,才能更好地执行相同的工作(因为它是专门为此工作而构build的),或者有充分的理由不要使用结构化的exception处理(但是除非处于非常低的水平,否则不会有这种情况)。

在大多数其他语言中, goto的唯一可接受的用法是退出嵌套循环。 即使在那里,将外部循环提升到自己的方法并使用return也是更好的select。

除此之外, goto是一个标志,没有足够的思想进入特定的代码段。


1支持goto现代语言实现了一些限制(例如goto不能跳入或跳出函数),但问题从根本上保持不变。

顺便提一下,其他语言特性也是如此,特别是例外。 并且通常有严格的规则来使用这些特征,例如不使用exception来控制非特殊程序stream程的规则。

那么,有一件事情总比goto's差, 奇怪的使用其他程序stream操作符来避免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 

C#中, switch语句不允许翻转 。 所以goto被用来把控制转移到一个特定的switch-case标签或默认标签。

例如:

 switch(value) { case 0: Console.Writeln("In case 0"); goto case 1; case 1: Console.Writeln("In case 1"); goto case 2; case 2: Console.Writeln("In case 2"); goto default; default: Console.Writeln("In default"); break; } 

编辑:“没有经过”规则有一个例外。 如果case语句没有代码,则允许使用贯穿。

#ifdef TONGUE_IN_CHEEK

Perl有一个goto ,可以让你实现穷人的尾巴呼叫。 😛

 sub factorial { my ($n, $acc) = (@_, 1); return $acc if $n < 1; @_ = ($n - 1, $acc * $n); goto &factorial; } 

#endif

好的,这跟C的goto没有关系。 更严重的是,我同意其他有关使用goto进行清理,或者执行Duff设备等的意见。 这是关于使用,而不是滥用。

(同样的注释可以应用于longjmp ,exceptions, call/cc等 – 它们有合法的用途,但是很容易被滥用,例如,完全抛出一个exception以逃避深度嵌套的控制结构非特殊情况)。

多年来,我写了超过几行汇编语言。 最终,每个高级语言都会编译成gotos。 好吧,叫他们“分支”或“跳跃”或其他什么,但他们是gotos。 任何人都可以写无损汇编?

现在可以肯定的是,你可以指出一个Fortran,C或者BASIC程序员,用goto来运行暴乱是意大利面意大利面的一个秘诀。 但答案并不是要避免,而是要慎重使用。

一把刀可以用来准备食物,释放某人或杀死某人。 我们是否因为害怕后者而刀刃不生? 同样的goto:不小心使用它阻碍,小心使用它有帮助。

我觉得有趣的是,有些人甚至会列举一些可以接受的情况,说所有其他用途都是不可接受的。 你真的认为你知道goto是expressionalgorithm的最佳select吗?

举例来说,我给你举个例子,这里没有人表示过:

今天,我正在编写代码插入一个哈希表中的元素。 散列表是先前计算的caching,可以随意覆盖(影响性能但不正确)。

哈希表中的每个桶都有4个槽,并且我有一堆标准来决定桶满时覆盖哪个元素。 现在这意味着三次通过一个桶,像这样:

 // Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) goto add; // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) goto add; // Additional passes go here... add: // element is written to the hash table here 

现在,如果我没有使用goto,这个代码是什么样的?

像这样的东西:

 // Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) break; if (add_index >= ELEMENTS_PER_BUCKET) { // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) break; if (add_index >= ELEMENTS_PER_BUCKET) // Additional passes go here (nested further)... } 

//元素在这里被写入哈希表

如果增加了更多的通行证,它会变得越来越糟,而带goto的版本始终保持相同的缩进级别,并避免使用虚假if语句,其结果被前一个循环的执行所隐含。

所以还有另外一个例子,goto使得代码更清晰,更易于编写和理解……我相信还有更多的东西,所以不要假装知道goto有用的所有情况,把你没有的好东西没想到。

看看在C编程时何时使用Goto :

虽然goto的使用几乎总是不好的编程习惯(当然,你可以find一个更好的方法来做XYZ),有时候它确实不是一个错误的select。 有些人甚至可能会说,如果有用的话,这是最好的select。

大部分我不得不说的goto只适用于C.如果你使用C ++,没有合理的理由来使用goto代替exception。 然而,在C中,你没有exception处理机制的力量,所以如果你想从你的程序逻辑的其余部分中分离error handling,并且你想避免在代码中多次重写清理代码,那么转到可以是一个不错的select。

我是什么意思? 你可能有一些如下所示的代码:

 int big_function() { /* do some work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* clean up*/ return [success]; } 

这很好,直到你意识到你需要改变你的清理代码。 那么你必须经过4次修改。 现在,您可能会决定将所有清理操作封装到一个函数中; 这不是一个坏主意。 但是这意味着你需要小心指针 – 如果你打算在清理函数中释放一个指针,除非传入一个指针指针,否则没有办法将它设置为NULL。 在很多情况下,你不会再使用这个指针,所以这可能不是一个主要的问题。 另一方面,如果你添加一个新的指针,文件句柄或其他需要清理的东西,那么你需要再次改变你的清理函数。 然后你需要改变这个函数的参数。

通过使用goto ,它会

 int big_function() { int ret_val = [success]; /* do some work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } end: /* clean up*/ return ret_val; } 

这样做的好处是您的代码跟随结束可以访问需要执行清理的所有内容,并且您已经设法大大减less了更改点的数量。 另一个好处是你已经从你的函数有多个退出点到只有一个; 没有清理的机会,你不可能不小心从function中返回。

而且,由于goto只是被用来跳转到一个单一的点上,所以并不像创build大量的意大利面代码来试图模拟函数调用。 相反,goto实际上有助于编写更多结构化的代码。


总之,应该总是谨慎地使用goto ,作为最后的手段 – 但是有时间和地点。 问题不应该是“你必须使用它”,而是“使用它是最好的select”。

我们使用的goto规则是goto可以跳转到函数中的单个出口清理点。 在非常复杂的function中,我们放松了这个规则以允许其他跳转。 在这两种情况下,我们都避免使用错误代码检查经常发生的深层嵌套if语句,这有助于可读性和维护性。

goto的原因之一是坏的,除了编码风格,你可以使用它来创build重叠 ,但非嵌套的循环:

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

这会造成奇怪的,但可能是合法的stream程控制结构,其中像(a,b,c,b,a,b,a,b,…)这样的序列是可能的,这使得编译器黑客不高兴。 显然有一些聪明的优化技巧,依靠这种types的结构不会发生。 (我应该检查我的龙书的副本…)这可能(使用一些编译器)的结果是其他优化不包含goto s代码。

如果你知道它,可能会很有用,“顺便说一下”,恰巧说服编译器发出更快的代码。 就个人而言,我宁愿尝试向编译器解释什么是可能的,什么是不使用goto之类的技巧之前,但可以说,我也可以在黑客汇编之前尝试goto

The most thoughtful and thorough discussion of goto statements, their legitimate uses, and alternative constructs that can be used in place of "virtuous goto statements" but can be abused as easily as goto statements, is Donald Knuth's article " Structured Programming with goto Statements ", in the December 1974 Computing Surveys (volume 6, no. 4. pp. 261 – 301).

Not surprisingly, some aspects of this 39-year old paper are dated: Orders-of-magnitude increases in processing power make some of Knuth's performance improvements unnoticeable for moderately sized problems, and new programming-language constructs have been invented since then. (For example, try-catch blocks subsume Zahn's Construct, although they are rarely used in that way.) But Knuth covers all sides of the argument, and should be required reading before anyone rehashes the issue yet again.

In a Perl module, you occasionally want to create subroutines or closures on the fly. The thing is, that once you have created the subroutine, how do you get to it. You could just call it, but then if the subroutine uses caller() it won't be as helpful as it could be. That is where the goto &subroutine variation can be helpful.

这里是一个简单的例子:

 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. } 

You can also use this form of goto to provide a rudimentary form of tail-call optimization.

 sub factorial($){ my($n,$tally) = (@_,1); return $tally if $n <= 1; $tally *= $n--; @_ = ($n,$tally); goto &factorial; } 

( In Perl 5 version 16 that would be better written as goto __SUB__; )

There is a module that will import a tail modifier and one that will import recur if you don't like using this form of 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 ); } 

Most of the other reasons to use goto are better done with other keywords.

Like redo ing a bit of code:

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

Or going to the last of a bit of code from multiple places:

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

如果是这样,为什么?

C has no multi-level/labelled break, and not all control flows can be easily modelled with C's iteration and decision primitives. gotos go a long way towards redressing these flaws.

Sometimes it's clearer to use a flag variable of some kind to effect a kind of pseudo-multi-level break, but it's not always superior to the goto (at least a goto allows one to easily determine where control goes to, unlike a flag variable), and sometimes you simply don't want to pay the performance price of flags/other contortions to avoid the goto.

libavcodec is a performance-sensitive piece of code. Direct expression of the control flow is probably a priority, because it'll tend to run better.

Just as well no one ever implemented the "COME FROM" statement….

I find the do{} while(false) usage utterly revolting. It is conceivable might convince me it is necessary in some odd case, but never that it is clean sensible code.

If you must do some such loop, why not make the dependence on the flag variable explicit?

 for (stepfailed=0 ; ! stepfailed ; /*empty*/) 

The GOTO can be used, of course, but there is one more important thing than the code style, or if the code is or not readable that you must have in mind when you use it: the code inside may not be as robust as you think .

For instance, look at the following two code snippets:

 If A <> 0 Then A = 0 EndIf Write("Value of A:" + A) 

An equivalent code with GOTO

 If A == 0 Then GOTO FINAL EndIf A = 0 FINAL: Write("Value of A:" + A) 

The first thing we think is that the result of both bits of code will be that "Value of A: 0" (we suppose an execution without parallelism, of course)

That's not correct: in the first sample, A will always be 0, but in the second sample (with the GOTO statement) A might not be 0. Why?

The reason is because from another point of the program I can insert a GOTO FINAL without controlling the value of A.

This example is very obvious, but as programs get more complicated, the difficulty of seeing those kind of things increases.

Related material can be found into the famous article from Mr. Dijkstra "A case against the GO TO statement"

1) The most common use of goto that I know of is emulating exception handling in languages that don't offer it, namely in C. (The code given by Nuclear above is just that.) Look at the Linux source code and you'll see a bazillion gotos used that way; there were about 100,000 gotos in Linux code according to a quick survey conducted in 2013: http://blog.regehr.org/archives/894 . Goto usage is even mentioned in the Linux coding style guide: https://www.kernel.org/doc/Documentation/CodingStyle . Just like object-oriented programming is emulated using structs populated with function pointers, goto has its place in C programming. So who is right: Dijkstra or Linus (and all Linux kernel coders)? It's theory vs. practice basically.

There is however the usual gotcha for not having compiler-level support and checks for common constructs/patterns: it's easier to use them wrong and introduce bugs without compile-time checks. Windows and Visual C++ but in C mode offer exception handling via SEH/VEH for this very reason: exceptions are useful even outside OOP languages, ie in a procedural language. But the compiler can't always save your bacon, even if it offers syntactic support for exceptions in the language. Consider as example of the latter case the famous Apple SSL "goto fail" bug, which just duplicated one goto with disastrous consequences ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

 if (something()) goto fail; goto fail; // copypasta bug printf("Never reached\n"); fail: // control jumps here 

You can have exactly the same bug using compiler-supported exceptions, eg in C++:

 struct Fail {}; try { if (something()) throw Fail(); throw Fail(); // copypasta bug printf("Never reached\n"); } catch (Fail&) { // control jumps here } 

But both variants of the bug can be avoided if the compiler analyzes and warns you about unreachable code. For example compiling with Visual C++ at the /W4 warning level finds the bug in both cases. Java for instance forbids unreachable code (where it can find it!) for a pretty good reason: it's likely to be a bug in the average Joe's code. As long as the goto construct doesn't allow targets that the compiler can't easily figure out, like gotos to computed addresses(**), it's not any harder for the compiler to find unreachable code inside a function with gotos than using Dijkstra-approved code.

(**) Footnote: Gotos to computed line numbers are possible in some versions of Basic, eg GOTO 10*x where x is a variable. Rather confusingly, in Fortran "computed goto" refers to a construct that is equivalent to a switch statement in C. Standard C doesn't allow computed gotos in the language, but only gotos to statically/syntactically declared labels. GNU C however has an extension to get the address of a label (the unary, prefix && operator) and also allows a goto to a variable of type void*. See https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html for more on this obscure sub-topic. The rest of this post ins't concerned with that obscure GNU C feature.

Standard C (ie not computed) gotos are not usually the reason why unreachable code can't be found at compile time. The usual reason is logic code like the following. 特定

 int computation1() { return 1; } int computation2() { return computation1(); } 

It's just as hard for a compiler to find unreachable code in any of the following 3 constructs:

 void tough1() { if (computation1() != computation2()) printf("Unreachable\n"); } void tough2() { if (computation1() == computation2()) goto out; printf("Unreachable\n"); out:; } struct Out{}; void tough3() { try { if (computation1() == computation2()) throw Out(); printf("Unreachable\n"); } catch (Out&) { } } 

(Excuse my brace-related coding style, but I tried to keep the examples as compact as possible.)

Visual C++ /W4 (even with /Ox) fails to find unreachable code in any of these, and as you probably know the problem of finding unreachable code is undecidable in general. (If you don't believe me about that: teaching/2006/OptComp/slides/lecture02.pdf )

As a related issue, the C goto can be used to emulate exceptions only inside the body of a function. The standard C library offers a setjmp() and longjmp() pair of functions for emulating non-local exits/exceptions, but those have some serious drawbacks compared to what other languages offer. The Wikipedia article http://en.wikipedia.org/wiki/Setjmp.h explains fairly well this latter issue. This function pair also works on Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), but hardly anyone uses them there because SEH/VEH is superior. Even on Unix, I think setjmp and longjmp are very seldom used.

2) I think the second most common use of goto in C is implementing multi-level break or multi-level continue, which is also a fairly uncontroversial use case. Recall that Java doesn't allow goto label, but allows break label or continue label. According to http://www.oracle.com/technetwork/java/simple-142616.html , this is actually the most common use case of gotos in C (90% they say), but in my subjective experience, system code tends to use gotos for error handling more often. Perhaps in scientific code or where the OS offers exception handling (Windows) then multi-level exits are the dominant use case. They don't really give any details as to the context of their survey.

Edited to add: it turns out these two use patterns are found in the C book of Kernighan and Ritchie, around page 60 (depending on edition). Another thing of note is that both use cases involve only forward gotos. And it turns out that MISRA C 2012 edition (unlike the 2004 edition) now permits gotos, as long as they are only forward ones.

In Perl, use of a label to "goto" from a loop – using a "last" statement, which is similar to break.

This allows better control over nested loops.

The traditional goto label is supported too, but I'm not sure there are too many instances where this is the only way to achieve what you want – subroutines and loops should suffice for most cases.

The problem with 'goto' and the most important argument of the 'goto-less programming' movement is, that if you use it too frequently your code, although it might behave correctly, becomes unreadable, unmaintainable, unreviewable etc. In 99.99% of the cases 'goto' leads to spaghetti code. Personally, I cannot think of any good reason as to why I would use 'goto'.

Edsger Dijkstra, a computer scientist that had major contributions on the field, was also famous for criticizing the use of GoTo. There's a short article about his argument on Wikipedia .

I use goto in the following case: when needed to return from funcions at different places, and before return some uninitialization needs to be done:

non-goto version:

 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 version:

 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; } 

The second version makes it easier, when you need to change something in the deallocation statements (each is used once in the code), and reduces the chance to skip any of them, when adding a new branch. Moving them in a function will not help here, because the deallocation can be done at different "levels".

Some say there is no reason for goto in C++. Some say that in 99% cases there are better alternatives. To be concrete, here's an example where goto leads to a nice code, something like enhanced do-while loop:

 int i; again: std::cout << "insert number: "; std::cin >> i; if(std::cin.fail()) { std::cin.clear(); std::cin.ignore(1000,'\n'); goto again; } std::cout << "your number is " << i; 

Compare it to goto-free code:

 int i; bool loop; do { loop = false; std::cout << "insert number: "; std::cin >> i; if(std::cin.fail()) { std::cin.clear(); std::cin.ignore(1000,'\n'); loop = true; } } while(loop); std::cout << "your number is " << i; 

I see these differences:

  • nested {} block is needed (albeit do {...} while looks more familiar)
  • extra loop variable is needed, used in four places
  • it takes longer time to read and understand the work with the loop
  • the loop does not hold any data, it just controls the flow of the execution, which is less comprehensible than simple label

The point is that goto can be easily misused, but goto itself is not to blame. Note that label has function scope in C++, so it does not pollute global scope like in pure assembly, in which overlapping loops has its place and are very common – like in the following code for 8051, where 7segment display is connected to P1. The program loops lightning segment around:

 ; P1 states loops ; 11111110 <- ; 11111101 | ; 11111011 | ; 11110111 | ; 11101111 | ; 11011111 | ; |_________| again: MOV P1,#11111110b ACALL delay loop: MOV A,P1 RL A MOV P1,A ACALL delay JNB P1.5, again SJMP loop