ループと関数をサポートする言語で「goto」を使用することの利点はありますか?もしそうなら、なぜですか?


201

私は長い間goto、可能であれば使用すべきではないという印象を受けてきました。先日、libavcodec(Cで書かれています)を調べていると、複数の使い方があることに気付きました。gotoループと関数をサポートする言語で使用することが有利になることはありますか?もしそうなら、なぜですか?

回答:


242

私が知っている "goto"ステートメントを使用する理由はいくつかあります(一部はすでにこれについて話しています):

関数をきれいに終了する

多くの場合、関数では、リソースを割り当て、複数の場所で終了する必要があります。プログラマは、関数の最後にリソースクリーンアップコードを配置することでコードを簡略化でき、関数のすべての「出口点」はクリーンアップラベルに移動します。このように、関数のすべての「出口点」でクリーンアップコードを記述する必要はありません。

ネストされたループを終了する

入れ子になったループで、すべてのループから抜け出す必要がある場合、gotoを使用すると、breakステートメントやifチェックよりもずっとクリーンでシンプルになります。

低レベルのパフォーマンスの改善

これは、パフォーマンスが重要なコードでのみ有効ですが、gotoステートメントは非常に高速に実行され、関数を移動するときに効果を発揮します。ただし、コンパイラは通常、gotoを含むコードを最適化できないため、これは両刃の剣です。

これらすべての例で、gotoは単一の関数のスコープに制限されていることに注意してください。


15
ネストされたループを終了する正しい方法は、内側のループを別のメソッドにリファクタリングすることです。
ジェイソン

129
@ジェイソン-バー。それはたくさんの雄牛です。交換gotoにはreturnちょうど愚かです。それは何も「リファクタリング」するのではなく、単に「名前を変更する」だけなので、goto抑圧された環境で育った人々(つまり、私たち全員)は、道徳的にに相当するものを使うことについてより良い気分になりますgoto。私はずっとループを参照することを好む私はそれを使用し、少し見gotoているそれだけで、ただのツールを参照してください誰かがちょうど避けるためにどこかに無関係なループを移動したよりも、goto
Chris Lutz、

18
gotosが重要な状況があります。たとえば、例外のないC ++環境です。Silverlightのソースでは、マクロを使用して安全な機能を実現するために数万(またはそれ以上)のgotoステートメントが存在します。主要なメディアコーデックとライブラリは多くの場合、戻り値を処理し、例外は発生しません。これらのエラー処理メカニズムを組み合わせることは困難です。単一のパフォーマンスの方法。
ジェフウィルコックス

79
それの価値すべてがあることを指摘breakcontinuereturn基本的にあるgotoだけで素敵なパッケージで、。
el.pescado 2010年

16
do{....}while(0)Javaで動作するという事実を除いて、gotoよりも優れたアイデアになるとは思いません。
ジェレミーリスト14

906

gotoEdsger DijkstraのGoToは有害であると見なされ、直接的または間接的にアンチサイトであるすべての人記事を反論彼らの立場を実証します。悪いダイクストラの記事は、最近のステートメントの使用方法とは実質的に何の関係もないためgoto、この記事で述べていることは、現代のプログラミングシーンにはほとんどまたはまったく適用できません。goto今の宗教上の-lessミームvergesは、その経典の右下には高い、その高司祭と認識される異端のシャニング(またはより悪い)上から指示されます。

ダイクストラの論文を文脈に当てはめて、主題に少し光を当てましょう。

ダイクストラが彼の論文を書いたとき、当時の一般的な言語は、BASIC、FORTRAN(初期の方言)、さまざまなアセンブリ言語などの非構造化手続き言語でした。高級言語を使用している人々が、「スパゲッティコード」という用語を生み出したねじれた、歪んだ実行スレッドでコードベース全体をジャンプすることはごく一般的でした。これは、Mike Mayfieldによって作成された古典的なトレックゲームに飛び乗って、物事がどのように機能するかを理解しようとすることで確認できます。少し時間をかけて、それを見てください。

これは、ダイクストラが1968年に彼の論文で非難していた「行き止まりのない使用の声明」です。 、これは彼がその論文を書くために彼を導いたことに住んでいた環境です。コードの好きな場所で好きな場所にジャンプできる機能は、彼が非難し、停止するように要求していたことです。それをgotoCや他のそのようなより現代的な言語の貧弱な力と比較することは簡単に信頼できます。

彼らが異端者に直面するとき、私はすでにカルティストの上げられた聖歌を聞くことができます。「しかし」彼らは「gotoCではコードを非常に読みにくくすることができる」と唱えます。そうそう?コードがないと、コードを非常に読みにくくすることができgotoます。このように:

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

ないgoto光景で、右、読みやすくなければなりませんので?またはこれはどうですか:

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*/

gotoそこにもありません。したがって、読み取り可能でなければなりません。

これらの例で私のポイントは何ですか?これは、コードを読みにくく、保守しにくくする言語機能ではありません。それを行うのは構文ではありません。これを引き起こすのは悪いプログラマです。そして、上記の項目でわかるように、悪いプログラマーは、どの言語機能も判読不能かつ使用不可能にする可能性があります。forそこまでのループのように。(見えますよね?)

公平を期すために、一部の言語構成要素は他の構成要素よりも悪用しやすいものです。しかし、あなたがCプログラマーなら、私は#define反対の十字軍に行くよりずっと前に、の使用の約50%をはるかに詳しく調べgotoます!

ですから、ここまで読んだことが気になった人にとって、注意すべき重要な点がいくつかあります。

  1. ダイクストラのgotoステートメントに関する論文gotoは、多くのプログラミング環境向けに書かれた より多くの潜在的にそれは、アセンブラではありません最も近代的な言語であるよりも、ダメージを与えます。
  2. のすべての使用を自動的に破棄 gotoこのため、「一度は楽しんだが、気に入らなかったので、今は反対だ」と言うのと同じくらい合理的です。
  3. 現代の(貧血)の正当な用途があります goto他の構成体で適切に置き換えることができないコードステートメントの。
  4. もちろん、同じステートメントの違法な使用があります。
  5. また、「godo」の代わりに、常に偽のdoループが使用さbreakれていない「」のような現代の制御ステートメントの不正な使用法もありますgoto。これらは、賢明なの使用よりも悪い場合がよくありgotoます。

42
@pocjoc:たとえば、Cで末尾再帰最適化を記述します。これは関数型言語インタープリター/ランタイムの実装で発生しますが、問題の興味深いカテゴリーの代表です。Cで記述されたほとんどのコンパイラーは、このためgotoを使用します。もちろん、それは作るためにそれが呼び出された状況でほとんどの状況では発生しません。そのAニッチな使用、多くの利用には意味のを。
zxq9 2013

66
Knuthの返信「構造化プログラミングとgo toステートメント」に言及せずに、ダイクストラの「有害と見なされることへの取り組み」の論文について、誰もが語るのはなぜですか。
オブロモフ

22
@ Nietzche-jou「これはかなり露骨なストローマンです[...]」彼は皮肉なことに、誤った二分法を使用して、なぜスティグマが非論理的であるかを示しています。Strawmanは論理的な誤りであり、意図的に相手の立場を誤って伝えて、相手の立場が簡単に破られるように見せかけます。例えば、「無神論者は神を憎むのでただ無神論者です」。誤った二分法とは、少なくとも1つの追加の可能性(つまり、白黒)が存在するときに、反対の極値が真であると想定した場合です。たとえば、「神は同性愛者を憎むので、彼は異性愛者を愛しています。」。
ブレーデンベスト

27
@JLRisheそこにもないものを深く読みすぎています。それを使用した人がそれが論理的に理にかなっていると正直に信じる場合、それは真の論理的誤りです。しかし、彼は皮肉っぽくそれを言った、それが彼がそれがばかげていることを知っていることをはっきりと示して、これが彼がそれを示している方法である。彼は「彼の主張を正当化するためにそれを使う」ことではありません。彼はそれを使って、gotosが自動的にコードをスパゲッティにするのはなぜばかげているのかを示しています。つまり、あなたはまだgotoなしで下手なコードを書くことができます。それが彼のポイントです。そして皮肉な二分法はユーモラスな方法でそれを説明する彼の方法です。
Braden Best

32
-1は、ダイクストラの発言が適用されない理由を非常に大きく主張する一方で、goto実際の利点を示すことを忘れている(これは投稿された質問です)
Maarten Bodewes 14年

154

盲目的にベストプラクティスに従うことは、ベストプラクティスではありません。gotoフロー制御の主要な形式としてステートメントを回避するという考えは、判読不能なスパゲッティコードの生成を回避することです。適切な場所で控えめに使用すると、アイデアを表現する最も簡単で明確な方法になることがあります。Zortech C ++コンパイラとDプログラミング言語の作成者であるWalter Brightは、それらを頻繁に、しかし慎重に使用しています。でもgotoステートメント、彼のコードは完全に読み取り可能です。

結論:回避gotoするために回避するのgotoは無意味です。本当に避けたいのは、読み取り不可能なコードを作成することです。あなたの場合はgoto-ladenコードが読み取り可能である、それで何も間違ってあります。


36

以来gotoプログラムについての推論ますが流れにくい1(別名「スパゲッティコード」。)、goto一般的な機能のみを欠けを補償するために使用される:の使用がgoto実際に許容できるかもしれないが、言語は、より構造化バリアントを提供していない場合にのみ取得します同じ目標。ダウトの例を見てみましょう:

私たちが使用するgotoのルールは、関数内の単一の終了クリーンアップポイントにジャンプするためにgotoが問題ないということです。

これは当てはまりますが、言語がクリーンアップコード(RAIIや finally)を)は、同じ処理をより特別に作成されているため)、または正当な理由がない場合に限ります。構造化された例外処理を採用する(ただし、非常に低いレベルを除いて、このケースが発生することはありません)。

他のほとんどの言語では、許容される唯一の使用法gotoは、ネストされたループを終了することです。そして、ほとんどの場合、外側のループを独自のメソッドに持ち上げてreturn代わりに使用する方が良いでしょう。

それ以外gotoは、特定のコードに十分な考慮が払われていないという兆候です。


1gotoいくつかの制限を実装することをサポートする現代の言語(たとえばgoto、関数にジャンプしたり、関数からジャンプしたりできない)が、問題は基本的に同じままです。

ちなみに、他の言語機能、特に例外を除いて、もちろん同じことが当てはまります。また、例外を使用して例外的でないプログラムフローを制御しないというルールなど、指定された場所でのみこれらの機能を使用するための厳格なルールが通常あります。


4
ここで興味がありますが、コードをクリーンアップするためにgotoを使用する場合はどうでしょうか。クリーンアップとは、メモリの割り当て解除だけでなく、エラーロギングも意味します。私はたくさんの投稿を読んでいましたが、どうやら、誰もログを出力するコードを書いていません。
シバ

37
「基本的に、gotoの不完全な性質のため(そしてこれは議論の余地がないと私は信じています)」 -1と私はそこで読むのをやめました。
o0 '。

14
gotoに対する警告は、初期のリターンも悪と見なされていた構造化プログラミング時代から来ています。ネストされたループを関数に移動すると、ある "悪"が別の "悪"と交換され、存在する本当の理由がない関数が作成されます( "gotoの使用を避けることができました!"以外)。gotoが制約なしで使用される場合にスパゲッティコードを生成するための単一の最も強力なツールであることは事実ですが、これはそのための完璧なアプリケーションです。コードを簡素化します。クヌースはこの使用法について主張し、ダイクストラは「私がgotoについてひどく独断的であると信じる罠に陥らないでください」と述べました。

5
finally?したがって、エラー処理以外のものに例外を使用することは良いですが、使用gotoは悪いですか?例外にはかなり適切な名前が付けられていると思います
クリスチャン

3
@マッド:私が(日常的に)遭遇するケースで、「存在する本当の理由がない」関数を作成することが最善の解決策です。理由:結果が読みやすい制御フローになるように、トップレベルの関数から詳細が削除されます。gotoが最も読みやすいコードを生成するような状況に個人的に遭遇することはありません。一方、私は単純な関数で複数のリターンを使用し、他の誰かが単一の出口点に行きたいと思うかもしれません。わかりやすい名前の関数にリファクタリングするよりもgotoの方が読みやすい反例を見たいです。
ToolmakerSteve 2014

35

ええと、常により悪いことが1つありgoto'sます。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

2
do{}while(false)私は慣用的であると考えることができると思います。同意しないでください:D
Thomas Eding

37
@trinithis:それが「慣用的」である場合、それは反後藤カルトのためだけです。よく見てみると、goto after_do_block;実際のことを言わずに、ただの言い方にすぎないことに気づくでしょう。そうでなければ...ちょうど1回だけ実行される「ループ」?私はそれを制御構造の乱用と呼んでいます。
cHao

5
@ThomasEding Edingあなたの主張には一つの例外があります。C / C ++プログラミングを行って#defineを使用しなければならなかったとしたら、それは{} while(0)が複数行のコードをカプセル化するための標準の1つであるということです。例えば: #define do {memcpy(a、b、1); something ++;} while(0)#define memcpy(a、b、1);
Ignas2526 2014

10
@ Ignas2526 #definesの数、時々使用するよりもはるかに悪い方法を非常にうまく示しgotoました:D
Luaan

3
+1は、人々が後藤を回避しようとする方法の非常に良いリストです。私は他の場所でそのようなテクニックについての言及を見てきましたが、これは素晴らしい簡潔なリストです。過去15年間で、私のチームはこれらのテクニックをまったく必要とせず、後藤を使用しなかった理由を考えます。複数のプログラマによる数十万行のコードを持つクライアント/サーバーアプリケーションでも。C#、java、PHP、python、javascript。クライアント、サーバー、アプリのコード。自慢もせず、1つの立場を改宗しないでも、最も明確な解決策としてgotoを求めている状況に遭遇する人もいれば、そうでない人もいるのは本当に不思議です
ToolmakerSteve

28

ではC#の スイッチ文のdoestフォールスルーができません。したがって、gotoは、特定のスイッチケースラベルまたはデフォルトラベルに制御を移すために使用されます

例えば:

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

編集:「フォールスルーなし」ルールには1つの例外があります。caseステートメントにコードがない場合、フォールスルーが許可されます。


3
-スイッチのフォールスルーは、.NET 2.0でサポートされてmsdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
ケースにコード本文がない場合のみ。コードがある場合は、gotoキーワードを使用する必要があります。
マシューホワイト、

26
この回答はとても面白いです-C#はフォールスルーを削除しました。なぜなら、多くの人が有害であると見なしているためです。この例では、goto(多くの人にとって有害とも見なされます)を使用して元の有害であると思われる動作を復活させていますが、全体的な結果は実際にはそれほど害がありません(コードはフォールスルーが意図的なものであることを明確にしているためです!)。
thomasrutter、

9
キーワードがGOTOという文字で書かれているからといって、それがgotoにならない。この提示されたケースは後藤ではありません。これは、フォールスルー用のswitchステートメント構造です。繰り返しになりますが、C#についてはあまりよく知りません。
Thomas Eding、2011年

1
さて、この場合、それはフォールスルーより少し多いgoto case 5:です(ケース1にいるときに言うことができるため)。ここでは、Konrad Rudolphの答えが正しいようです。goto欠落している機能を補っています(実際の機能ほど明確ではありません)。私たちが本当に望んでいるのがフォールスルーである場合、おそらく最良のデフォルトはフォールスルーではなくcontinue、明示的に要求するようなものです。
David Stone、

14

#ifdef TONGUE_IN_CHEEK

Perlにはgoto貧乏人の尻尾の呼び出しを実装できるがあります。:-P

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

#endif

OK、Cとは関係ありませんgoto。もっと真剣に、私gotoはクリーンアップのために、またはダフのデバイスを実装するための使用についての他のコメントに同意しますます。それは悪用ではなく、使用に関するすべてです。

(同じコメントがlongjmp、例外、call/ccなどに適用できます---それらは正当な用途がありますが、簡単に悪用される可能性があります。たとえば、完全に例外のない状況下で、深くネストされた制御構造をエスケープするために純粋に例外をスローする。)


これがPerlでgotoを使用する唯一の理由だと思います。
Brad Gilbert

12

長年にわたって私は数行以上のアセンブリ言語を書いてきました。最終的に、すべての高水準言語はゴトにコンパイルされます。さて、それらを「ブランチ」や「ジャンプ」などと呼びますが、それらはゴトです。誰でもgotoなしのアセンブラを作成できますか?

これで、Fortran、C、またはBASICプログラマーに、gotoで暴動を実行するのはスパゲッティボロネーズのレシピであることを指摘できます。しかし、答えはそれらを回避することではなく、慎重に使用することです。

ナイフは、食事の準備、誰かの解放、または誰かの殺害に使用できます。私たちは後者を恐れてナイフなしでやっていますか?同様にgoto:不注意に使用すると邪魔になり、慎重に使用すると役立ちます。


1
おそらく私がこれが根本的に間違っていると私が思う理由をstackoverflow.com/questions/46586/…でお
Konrad Rudolph

15
「とにかくすべてがJMPにコンパイルされる!」議論は基本的に高級言語でのプログラミングの背後にあるポイントを理解していません。
Nietzche-jou

7
本当に必要なのは、減算と分岐だけです。その他はすべて、利便性またはパフォーマンスのためのものです。
デビッドストーン

8

CでプログラミングするときにGotoを使用する場合を見てください

gotoの使用は、ほとんどの場合悪いプログラミング習慣です(確かに、XYZを実行するより良い方法を見つけることができます)が、それが本当に悪い選択ではない場合もあります。一部の人は、それが有用である場合、それが最良の選択であるとさえ主張するかもしれません。

gotoについて私が言わなければならないことのほとんどは、実際にはCにのみ当てはまります。C++を使用している場合、例外の代わりにgotoを使用する正当な理由はありません。ただし、Cでは、例外処理メカニズムの機能はありません。そのため、エラー処理を残りのプログラムロジックから分離し、コード全体でクリーンアップコードを何度も書き直さないようにしたい場合は、次にgotoを選択するのが良いでしょう。

どういう意味?次のようなコードがあるかもしれません:

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

ここでの利点は、コードの後のコードがクリーンアップを実行するために必要なすべてにアクセスでき、変更点の数を大幅に削減できたことです。もう1つの利点は、関数の複数の出口点を1つにしたことです。クリーンアップせずに関数から誤って戻る可能性はありません。

さらに、gotoは1つのポイントにジャンプするためだけに使用されているため、関数呼び出しをシミュレートするために前後にジャンプするスパゲッティコードの大部分を作成しているわけではありません。むしろ、gotoは実際にはより構造化されたコードを書くのに役立ちます。


つまり、goto常に控えめに、最後の手段として使用する必要がありますが、それには時間と場所があります。問題は、「使用する必要があるか」ではなく、「使用するのが最善か」ということです。


7

gotoが悪い理由の1つは、コーディングスタイルに加えて、それを使用してオーバーラップしているがネストされてないループを作成できることです。

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

これは奇妙ですが、(a、b、c、b、a、b、a、b、...)のようなシーケンスが可能な合法的な制御フロー構造を作成し、コンパイラのハッカーを不幸にします。明らかに、このタイプの構造が発生しないことに依存する巧妙な最適化トリックがいくつかあります。(ドラゴンブックのコピーを確認する必要があります...)この結果(一部のコンパイラを使用)、gotos を含むコードに対して他の最適化が行われない可能性があります。

「ああ、ちなみに」それがわかっているだけで、コンパイラがより高速なコードを出力するように説得する場合に便利です。個人的には、gotoのようなトリックを使用する前に、可能性のあるものとそうでないものについてコンパイラーに説明したいと思いますが、おそらく、gotoアセンブラーをハッキングする前に試すこともできます。


3
さて...金融情報会社でFORTRANをプログラミングしていた時代に私を戻します。2007
。– Marcin、

5
言語構造は、読み取り不能またはパフォーマンスの低下を招く方法で悪用される可能性があります。
私の正しい意見2010年

@JUST:要点は読みやすさやパフォーマンスの低下ではなく、フローオブコントロールグラフに関する仮定と保証です。gotoを乱用すると、パフォーマンス(または読みやすさ)向上します。
Anders Eurenius

3
私は、理由の一つが主張したいgotoです便利な、それはあなたがそうでなければ論理的なねじれの束を必要とするこのようなループを構築することを可能にするということです。さらに、オプティマイザがこれを書き換える方法を知らない場合は、良いと主張します。このようなループは、パフォーマンスや読みやすさのために行うべきではありませんが、それは、物事が発生する必要がある順序とまったく同じだからです。その場合、私はオプティマイザがそれをいじくり回すことを特に望まないでしょう。
cHao 2014年

1
...必要なアルゴリズムをGOTOベースのコードに直接変換すると、混乱したフラグ変数や乱用されたループ構造よりも検証がはるかに簡単になります。
スーパーキャット2014

7

他のすべての用途は受け入れられないと言って、gotoが受け入れられるケースのリストを提供する人もいるのは面白いと思います。gotoがアルゴリズムを表現するための最良の選択であるすべてのケースを知っていると本当に思いますか?

説明のために、ここではまだ誰も示していない例を紹介します。

今日、ハッシュテーブルに要素を挿入するためのコードを書いていました。ハッシュテーブルは以前の計算のキャッシュであり、任意に上書きできます(パフォーマンスに影響を与えますが、正確性には影響しません)。

ハッシュテーブルの各バケットには4つのスロットがあり、バケットがいっぱいになったときに上書きする要素を決定するための基準がたくさんあります。現時点では、これは次のようにバケットを最大3回通過することを意味します。

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

// element is written to the hash table here

gotoを使用したバージョンでは常に同じインデントレベルが維持され、前のループの実行によって結果が暗示される偽のifステートメントの使用が回避される一方で、パスがさらに追加されると、どんどん悪くなります。

だからgotoがコードをよりクリーンで簡単に記述して理解できる別のケースがあります...きっともっとたくさんあるので、gotoが役立つすべてのケースを知っているふりをしないでください。考えない。


1
あなたが与えた例では、それをかなり大幅にリファクタリングしたいと思います。一般に、私は次のコードのチャンクが何をしているかを示す一行コメントを避けようとします。代わりに、コメントに似た名前の独自の関数に分割します。このような変換を行う場合、この関数は、関数が実行していることの高レベルの概要を提供し、新しい関数はそれぞれ、各ステップの実行方法を示します。goto各機能を抽象化の同じレベルにすることは、他のどんな反対よりもはるかに重要だと私は思います。それが避けることgotoはボーナスです。
David Stone、

2
関数を追加してgoto、複数のインデントレベル、偽のifステートメントを取り除く方法についてはまだ説明していません...
Ricardo

標準のコンテナ表記を使用すると、次のようになります。このcontainer::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();種のプログラミングははるかに読みやすくなっています。この場合の各関数は、純粋な関数として記述できます。メイン関数は、コードが関数の名前だけで何を行うかを説明します。必要に応じて、それらの定義を見て、それがどのように行われるかを確認できます。
David Stone

3
特定のアルゴリズムが自然にgotoをどのように使用すべきかについて、誰もが例を挙げなければならないという事実は、今日のアルゴリズム的思考がどれほど少ないかについて悲しい反省です!! もちろん、@ Ricardoの例は、gotoがエレガントで明白な場所の(多くの1つ)完璧な例です。
Fattie 2014年

6

私たちが使用するgotoのルールは、関数内の単一の終了クリーンアップポイントにジャンプするためにgotoが問題ないということです。本当に複雑な関数では、そのルールを緩和して他のジャンプを許可します。どちらの場合も、エラーコードのチェックで頻繁に発生するifステートメントが深くネストされることを避けています。これにより、読みやすさと保守が容易になります。


1
Cのような言語でそのようなものが役に立つのを見ることができました。しかし、C ++コンストラクタ/デストラクタの能力を持っている場合、それは一般的にそれほど役に立ちません。
David Stone

1
「本当に複雑な関数では、他のジャンプを許可するためにそのルールを緩和します」ジャンプを使用して複雑な関数をさらに複雑にする場合、例がないように聞こえます。これらの複雑な関数をリファクタリングして分割することは、「より良い」アプローチではないでしょうか?
MikeMB 2015

1
これがgotoを使用する最後の目的でした。Javaでgotoを見逃すことはありません。なぜなら、同じことを実行できるtry-finallyがあるからです。
パトリシアシャナハン2016年

5

gotoステートメント、その正当な用途、および「仮想gotoステートメント」の代わりに使用でき、gotoステートメントと同じくらい簡単に悪用できる代替構成体の最も思慮深く徹底的な議論は、Donald Knuthの記事「gotoステートメントによる構造化プログラミング」です。」です。 、1974年12月のコンピューティング調査(第6巻、第4号、261〜301ページ)。

当然のことながら、この39年の古い論文のいくつかの側面は古くなっています。処理能力の桁違いの増加により、中程度のサイズの問題ではKnuthのパフォーマンスの向上の一部が目立たなくなり、それ以降、新しいプログラミング言語の構成要素が発明されました。(たとえば、try-catchブロックはZahnの構成を包含しますが、そのように使用されることはめったにありません。)しかし、Knuthは議論のすべての側面をカバーしており、誰もが再び問題を再検討する前に読む必要があります。


3

Perlモジュールでは、その場でサブルーチンやクロージャーを作成したい場合があります。問題は、いったんサブルーチンを作成したら、どのようにしてそれに到達するかです。あなたはそれを呼び出すことができますが、サブルーチンがcaller()それを使用する場合、それができるほど役立つことはありません。そこで、goto &subroutineバリエーションが役立ちます。

ここに簡単な例があります:

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バージョン16では、次のように記述した方がよいgoto __SUB__;

tailモディファイアをインポートするモジュールと、recurこの形式のを使用したくない場合にインポートするモジュールがあります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のキーワードを使用する方が適切です。

redo少しコードを書くように:

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

またはlast、複数の場所から少しのコードに移動します。

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

2

もしそうなら、なぜですか?

Cにはマルチレベル/ラベル付きブレークがなく、すべての制御フローをCの反復プリミティブおよび決定プリミティブで簡単にモデル化できるわけではありません。gotosは、これらの欠陥を修正するのに大いに役立ちます。

ある種のフラグ変数を使用して一種の疑似マルチレベルのブレークを実行する方が明確な場合がありますが、常にgotoより優れているわけではありません(少なくともgotoを使用すると、フラグ変数とは異なり、どこに制御が移動するかを簡単に判断できます)、そしてときどき、gotoを回避するためにフラグやその他のゆがみのパフォーマンス価格を支払いたくないだけの場合もあります。

libavcodecは、パフォーマンスに敏感なコードです。制御フローは直接実行するほうが優先される可能性があります。これは、実行速度が向上する傾向があるためです。


2

同様に、誰も「COME FROM」ステートメントを実装したことがありません...


8
またはC ++、C#、Java、JS、Python、Ruby ...などなど。「例外」と呼ぶだけです。
cHao

2

do {} while(false)の使用法は完全に反抗的です。奇妙なケースで必要であると私に納得させるかもしれないと考えられるかもしれませんが、それがクリーンで賢明なコードであるとは決して言えません。

このようなループを実行する必要がある場合は、フラグ変数への依存を明示的にしないのはなぜですか?

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

ことはすべきではない/*empty*/ことstepfailed = 1?いずれにせよ、これはaよりも優れていdo{}while(0)ますか?両方とも、あなたbreakはそれから(またはあなたのものでstepfailed = 1; continue;)抜け出す必要があります。私には不要のようです。
Thomas Eding、2011年

2

1)私が知っているgotoの最も一般的な使用法は、それを提供しない言語、つまりCで例外処理をエミュレートすることです(上記のNuclearによって提供されたコードはそれだけです)。Linuxソースコードを見てください。バズリオンのgotoがそのように使用されるのがわかります。2013年に実施された簡単な調査によると、Linuxコードには約100,000個のgotoがありました:http ://blog.regehr.org/archives/894 。Gotoの使用法は、Linuxコーディングスタイルガイドhttps://www.kernel.org/doc/Documentation/CodingStyleでも言及されています。オブジェクト指向プログラミングが、関数ポインターが入力された構造体を使用してエミュレートされるのと同様に、gotoはCプログラミングでその位置を占めています。それでは、誰が正しいのでしょうか?ダイクストラまたはライナス(およびすべてのLinuxカーネルプログラマ)基本的には理論対実践です。

ただし、コンパイラレベルのサポートがないことと、一般的な構成/パターンをチェックしないことには通常の落とし穴があります。それらを間違って使用し、コンパイル時のチェックなしにバグを導入する方が簡単です。WindowsとVisual C ++ですが、Cモードでは、SEH / VEHを介して例外処理を提供します。これはまさにその理由です。例外は、OOP言語の外、つまり手続き型言語でも役立ちます。ただし、コンパイラが言語の例外の構文サポートを提供している場合でも、常にベーコンを保存できるとは限りません。後者の例として、1つのgotoを複製して悲惨な結果をもたらした有名なApple SSLの「goto fail」バグを考えてみます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

コンパイラがサポートする例外を使用して、たとえばC ++でまったく同じバグが発生する可能性があります。

struct Fail {};

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

ただし、コンパイラーが到達不能コードを分析して警告する場合は、バグの両方のバリアントを回避できます。たとえば、/ W4警告レベルでVisual C ++を使用してコンパイルすると、どちらの場合でもバグが見つかります。たとえば、Javaは到達不可能なコードを(それを見つけることができる場所で)禁止しています。goto構文が、計算されたアドレス(**)へのgotosのように、コンパイラーが簡単に理解できないターゲットを許可しない限り、コンパイラーがDijkstraを使用するよりもgotoを使用して関数内の到達不能コードを見つけることは難しくありません-承認済みコード。

(**)脚注:Basicの一部のバージョンでは、計算された行番号へのGotoが可能です。たとえば、GOTO 10 * x(xは変数です)。やや紛らわしいことに、Fortranの「計算されたgoto」は、Cのswitchステートメントに相当する構成を指します。標準Cでは、言語で計算されたgotoを許可していませんが、静的/構文的に宣言されたラベルへのgotoのみを許可しています。ただし、GNU Cには、ラベルのアドレス(単項、接頭辞&&演算子)を取得する拡張機能があり、void *型の変数へのgotoも許可されています。このあいまいなサブトピックの詳細については、https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlを参照してください。この投稿の残りの部分は、そのあいまいなGNU C機能には関係していません。

標準C(つまり、計算されない)のgotoは、通常、コンパイル時に到達できないコードが見つからない理由ではありません。通常の理由は、次のような論理コードです。与えられた

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

コンパイラが次の3つの構成要素のいずれかで到達できないコードを見つけるのと同じくらい困難です。

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&) {
  }
}

(ブレース関連のコーディングスタイルはすみませんが、サンプルをできるだけコンパクトに保つ​​ようにしました。)

Visual C ++ / W4(/ Oxを使用した場合でも)は、これらのいずれかで到達不能なコードを見つけることができません。また、おそらくご存知のように、到達不能なコードを見つけることの問題は一般に決定不可能です。(あなたがそれについて私を信じていない場合:https : //www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf

関連する問題として、C gotoは、関数の本体内でのみ例外をエミュレートするために使用できます。標準Cライブラリは、ローカル以外の出口/例外をエミュレートするための関数のsetjmp()およびlongjmp()ペアを提供しますが、他の言語が提供するものと比較すると、いくつかの重大な欠点があります。ウィキペディアの記事http://en.wikipedia.org/wiki/Setjmp.hは、この後者の問題をかなりよく説明しています。この関数のペアはWindows(http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx)でも機能しますが、SEH / VEHが優れているため、そこで使用されることはほとんどありません。Unixでも、setjmpとlongjmpはほとんど使用されていないと思います。

2)Cでのgotoの2番目に一般的な使用法は、マルチレベルのブレークまたはマルチレベルの継続の実装であると思います。これは、かなり議論の余地のないユースケースでもあります。Javaではgotoラベルは許可されていませんが、breakラベルまたはcontinueラベルは許可されています。http://www.oracle.com/technetwork/java/simple-142616.htmlによると、これは実際にはCでのgotoの最も一般的な使用例です(90%は彼らが言う)が、私の主観的な経験では、システムコードはエラー処理にgotosをより頻繁に使用する。おそらく科学的なコードやOSが例外処理を提供する場合(Windows)、マルチレベルの出口が主な使用例です。彼らは実際に彼らの調査のコンテキストについての詳細を提供しません。

追加のために編集:これらの2つの使用パターンは、60ページ(エディションによって異なります)の周りのKernighanとRitchieのCブックにあることがわかりました。もう1つの注目すべき点は、どちらのユースケースにも前方ゴトのみが含まれることです。そして、MISRA C 2012エディション(2004エディションとは異なります)は、フォワードのみである限り、gotoを許可することがわかりました。


正しい。「部屋の中の象」とは、善のために、世界的に重要なコードベースの一例としてのLinuxカーネルにgotoがロードされていることです。もちろん。明らかに。「アンチゴトミーム」は、数十年前からの好奇心に過ぎません。もちろん、プログラミングには多くのものがあり(特に「静的」、実際には「グローバル」、たとえば「elseif」)、非専門家によって悪用される可能性があります。それで、もしあなたが子供ならいとこがlearnin 2プログラムなら、あなたは彼らに「ああ、グローバルを使わない」と「決してelseifを使わない」と伝えます。
Fattie、2014年

goto failバグはgotoとは関係ありません。この問題は、その後に中括弧のないifステートメントが原因で発生します。2度貼り付けられたステートメントのコピーが問題になるだけでした。gotoよりもはるかに有害な場合は、そのタイプの裸のブレースレスを検討します。
muusbolla

2

C ++でgotoを実行する理由はないと言う人もいます。99%のケースではより良い代替案があると言う人もいます。これは推論ではなく、不合理な印象です。gotoが拡張されたdo-whileループのような優れたコードにつながる確かな例を次に示します。

int i;

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

std::cout << "your number is " << i;

goto-freeコードと比較してください:

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;

私はこれらの違いを見ます:

  • ネストされた{}ブロックが必要です(do {...} while見慣れたものです)
  • loop4つの場所で使用される追加の変数が必要です
  • との作業を読んで理解するのに時間がかかる loop
  • これloopはデータを保持せず、実行のフローを制御するだけです。これは単純なラベルよりも理解しにくいです

別の例があります

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

今度は「悪」なgotoを取り除きましょう:

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

gotoを使用するのと同じタイプであり、適切に構造化されたパターンであり、唯一の推奨される方法よりも多くのプロモートではないgotoです。確かに、次のような「スマートな」コードは避けたいでしょう。

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

重要なのは、gotoは簡単に誤用される可能性があるということですが、goto自体に責任があるわけではありません。ラベルはC ++で関数スコープを持っているため、重複するループがその場所にあり、非常に一般的である純粋なアセンブリのようにグローバルスコープを汚染しないことに注意してください。次の8051のコードのように、7セグメントディスプレイがP1に接続されています。プログラムは稲妻セグメントをループします:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

別の利点があります。gotoは名前付きループ、条件、その他のフローとして機能します。

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

または、インデント付きの同等のgotoを使用して、ラベル名を適切に選択すればコメントは不要です。

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

Perlでは、ループから「goto」するためにラベルを使用します。breakに似た「last」ステートメントを使用します。

これにより、ネストされたループをより適切に制御できます。

従来のgoto ラベルもサポートされていますが、これがあなたが望むものを達成する唯一の方法であるインスタンスが多すぎるとは思いません。ほとんどの場合、サブルーチンとループで十分です。


Perlで使用するgotoの唯一の形式はだと思いますgoto &subroutine。スタック内の現在のサブルーチンを置き換えながら、現在の@_でサブルーチンを開始します。
ブラッドギルバート

1

「goto」の問題と「goto-less programming」運動の最も重要な議論は、コードを頻繁に使用すると、正しく動作する可能性はあるものの、読み取り不能、保守不能、レビュー不能などになることです。99.99%で'goto'の場合は、スパゲッティコードになります。個人的には「後藤」を使う理由はなかなか思いつきません。


11
あなたの議論で「私は何の理由も考えられない」と言うことは、正式にはen.wikipedia.org/wiki/Argument_from_ignoranceですが、私は「想像力の欠如による証明」という用語を好みます。
私の正しい意見だけで

2
@ちょうど私の正しい意見:それが事後的に採用された場合にのみ、それは論理的な誤りです。言語設計者の観点からは、機能を含めるコストを比較検討することは有効な議論かもしれません(goto)。@cscholの使用法も同様です。現在言語を設計していない可能性がありますが、基本的にはデザイナーの努力を評価しています。
コンラートルドルフ

1
@KonradRudolph:IMHO、goto変数が存在するコンテキストを除いて言語を許可する方が、誰かが必要とする可能性のあるあらゆる種類の制御構造をサポートしようとするよりも安くなる傾向があります。でコードを書くgotoことは他の構造を使用するよりも良くないかもしれませんが、そのようなコードを書くことができることはgoto「表現力の穴」(言語が効率的なコードを書くことができない構成)を避けるのに役立ちます。
スーパーキャット2014

1
@supercat言語デザインの根本的に異なる学校の出身だと思います。私は言語が理解しやすさ(または正確さ)を犠牲にして最大限に表現力を発揮することに反対しています。寛容な言葉よりも制限的な言葉を使いたい。
Konrad Rudolph

1
@thbはい、もちろんそうです。それは多くの場合、コードが行う多くの困難について読むと推論します。事実上、誰かgotoがコードレビューサイトに含まれているコードを投稿するたびにgoto、コードのロジックが大幅に簡素化されます。
コンラートルドルフ

1

もちろんGOTOを使用することもできますが、コードスタイルよりも重要な点が1つあります。つまり、コードを読みやすくしたり読み取ったりできない場合、使用時に注意が必要です。内部のコードは、あなたほど堅牢ではない可能性があります。と思います

たとえば、次の2つのコードスニペットを見てください。

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

GOTOと同等のコード

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

私たちが最初に考えることは、コードの両方のビットの結果は "Value of A:0"になるということです(もちろん、並列処理なしの実行を想定しています)。

それは不正解です。最初のサンプルでは、​​Aは常に0ですが、2番目のサンプル(GOTOステートメントを使用)では、Aは0ではない可能性があります。なぜですか?

その理由は、プログラムの別のポイントからGOTO FINAL、Aの値を制御せずにを挿入できるからです。

この例は非常に明白ですが、プログラムが複雑になるにつれて、そのようなものを見ることの難しさが増します。

関連資料は、ダイクストラ氏の有名な記事「GO TOステートメントに対する訴訟」にあります。


6
これは、昔ながらのBASICによく見られました。ただし、最近のバリアントでは、別の関数の途中にジャンプすることはできません。多くの場合、変数の宣言を通過した後でも、基本的に(意図されていませんが)、現代の言語は、ダイクストラが話していた「束縛されていない」GOTOをほとんど取り除きました...そして、彼が反対していたときにそれを使用する唯一の方法は、他の特定の凶悪な罪を犯すことです。:)
cHao 2014年

1

私は次の場合にgotoを使用します。異なる場所の関数から戻る必要があり、戻る前に初期化を行う必要があります。

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

2番目のバージョンでは、割り当て解除ステートメントで何かを変更する必要がある場合(それぞれがコードで1回使用されます)が簡単になり、新しいブランチを追加するときにそれらのいずれかをスキップする可能性が低くなります。関数内でそれらを移動しても、割り当て解除はさまざまな「レベル」で実行できるため、ここでは役に立ちません。


3
これがfinally、C#にブロックがある理由です
John Saunders

^ @JohnSaundersが言ったように。これは、「言語に適切な制御構造がないためgotoを使用する」の例です。ただし、戻りの近くに複数のgotoポイントを必要とするのはコードのにおいです。より安全な(めちゃくちゃにならない方が簡単)および gotoを必要しないプログラミングスタイルがあり、これは「最終的に」のない言語でも問題なく機能します。アップが必要です。クリーンアップ以外のすべてを因数分解して、複数リターン設計を使用するメソッドにします。そのメソッドを呼び出してから、クリーンアップ呼び出しを行います。
ToolmakerSteve 2014

ここで説明するアプローチでは、追加レベルのメソッド呼び出しが必要です(ただし、が不足している言語でのみfinally)。別の方法として、gotosを使用しますが、常にすべてのクリーンアップを実行する共通の出口点に移動します。ただし、各クリーンアップメソッドは、nullまたは既にクリーンな値を処理できるか、条件テストによって保護されているため、適切でない場合はスキップされます。
ToolmakerSteve 2014

@ToolmakerSteveこれはコードの匂いではありません。実際、これはCで非常に一般的なパターンであり、gotoを使用する最も有効な方法の1つと考えられています。この関数からのクリーンアップを処理するためだけに、それぞれに不要なifテストがある5つのメソッドを作成する必要がありますか?これでコードとパフォーマンスの肥大化が完了しました。または、gotoを使用することもできます。
muusbolla

@muusbolla-1)「5つのメソッドを作成する」-いいえ。null 以外の値を持つリソースをクリーンアップする単一のメソッドを提案しています。またはgoto、すべて同じ出口点に行くs を使用する私の代替案を参照してください。これは同じロジックを持っています(これは、リソースごとにifが必要なように追加が必要です)。しかし、気にしCないでください。正しいコードを使用している場合、コードがCで記述されている理由が何であれ、ほとんどの場合、最も「直接的な」コードを優先するトレードオフになります。(私の提案ハンドル任意のリソースがまたは過剰、この場合には、ええ割り当てられた。しかしされていない可能性があり複雑な状況を。。)
ToolmakerSteve

0

分野に多大な貢献をしたコンピュータサイエンティストのEdsger Dijkstraも、GoToの使用を批判したことで有名でした。ウィキペディアで彼の議論についての短い記事があります。


0

時々、文字単位の文字列処理に便利です。

次のprintf-esqueの例のようなものを想像してみてください。

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

これgoto handle_literalを関数呼び出しにリファクタリングすることもできますが、複数の異なるローカル変数を変更する場合は、言語が可変クロージャをサポートしていない限り、それぞれに参照を渡す必要があります。continueロジックでelseケースが機能しない場合は、呼び出し後にステートメント(おそらくgotoの形式の1つ)を使用して同じセマンティクスを取得する必要があります。

私はまた、典型的には同様のケースで、レクサーでgotoを慎重に使用しました。ほとんどの場合それらは必要ありませんが、これらの奇妙なケースに備えておくと便利です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.