web-dev-qa-db-ja.com

「goto」ステートメントはどのようなバグを引き起こしますか?歴史的に重要な例はありますか?

ループにネストされたループから抜け出すために保存することを理解しています。 gotoステートメントは、バグが発生しやすいプログラミングスタイルとして回避および悪用され、決して使用されません。

XKCD Alt Text: "Neal Stephensonは、彼のラベルに「dengo」という名前を付けるのはかわいいと考えています"
元のコミックを参照してください: http://xkcd.com/292/

私はこれを早く学んだからです。 gotoが実際にどのような種類のバグにつながるのかについて、私はまったく洞察や経験をしていません。ここで何について話しているのですか?

  • 不安定?
  • 保守不能または読み取り不可能なコード?
  • セキュリティの脆弱性?
  • 他に完全に何か?

「goto」ステートメントは実際にはどのようなバグを引き起こしますか?歴史的に重要な例はありますか?

105
Akiva

これは、他の回答ではまだ見たことがない方法です。

スコープについてです。優れたプログラミングプラクティスの主要な柱の1つは、スコープを狭く保つことです。いくつかの論理的な手順以上のものを監督および理解する精神的能力が不足しているため、厳しいスコープが必要です。したがって、小さなブロック(それぞれが「1つのもの」になる)を作成し、それらを使用して1つのものになる大きなブロックを作成します。これにより、管理と理解が容易になります。

Gotoは、ロジックのスコープをプログラム全体に効果的に拡大します。これは、ほんの数行に及ぶ最小のプログラムを除いて、あなたの脳を倒すことは確実です。

それで、あなたが取り込んであなたのかわいそうな頭をチェックするには余りにもたくさんあるので、あなたはもう間違いを犯したかどうかを見分けることができません。これは本当の問題であり、バグはおそらく起こり得る結果です。

2
Martin Maat

goto自体が悪いわけではありません。 (結局のところ、コンピューター内のすべてのジャンプ命令は後藤です。)問題は、「フローチャート」プログラミングと呼ばれることがある、構造化プログラミングよりも古いプログラミングの人間スタイルがあることです。

フローチャートプログラミング(私の世代の人々が学び、アポロムーンプログラムに使用されました)では、ステートメント実行のブロックと決定のひし形の図を作成し、それらをあちこちにある線で接続できます。 (いわゆる「スパゲッティコード」)

スパゲッティコードの問題は、プログラマであるあなたがそれが正しいことを「知っている」ことができるということですが、自分や他の人にそれをどのように証明することができますか?実際、実際には誤動作する可能性があり、常に正しいという知識は間違っている可能性があります。

構造化プログラミングが始まり、終了ブロック、for、while、if-elseなどが登場しました。これらには、それでも何でもできるという利点がありましたが、慎重に行えば、コードが正しいことを確認できます。

もちろん、gotoがなくてもスパゲッティコードを書くことはできます。一般的な方法は、while(...) switch( iState ){...を記述することです。この場合、iStateを異なる値に設定します。実際、CまたはC++では、そのためのマクロを記述してGOTOという名前を付けることができます。そのため、gotoを使用していないことは、違いのない違いです。

コード提供が無制限のgotoを排除する方法の例として、ずっと前に、動的に変化するユーザーインターフェイスに役立つ制御構造を偶然見つけました。私はそれを differential execution と呼びました。それは完全にチューリングユニバーサルですが、その正確性の証明は純粋な構造化プログラミングに依存しています-gotoreturncontinuebreak、または例外はありません。

enter image description here

69
Mike Dunlavey

後藤はなぜ危険なのですか?

  • goto自体が不安定になることはありません。 約100,000 gotos、Linuxカーネル にもかかわらず、安定性のモデルです。
  • goto自体がセキュリティの脆弱性を引き起こすことはありません。ただし、一部の言語では、これをtry/catch例外管理ブロックと混在させると、この CERT推奨 で説明されているように、脆弱性につながる可能性があります。主流のC++コンパイラーはそのようなエラーにフラグを立てて防止しますが、残念ながら古いまたはよりエキゾチックなコンパイラーはそうしません。
  • gotoは、読み取り不能で保守不可能なコードを引き起こします。これは spaghetti code とも呼ばれます。これは、スパゲッティプレートのように、ゴトが多すぎると制御のフローをたどることが非常に難しいためです。

スパゲッティコードを回避でき、少数のgotoしか使用しない場合でも、次のようなバグやリソースリークが発生しやすくなります。

  • 明確にネストされたブロックとループまたはスイッチを備えた構造プログラミングを使用したコードは、簡単に追跡できます。その制御フローは非常に予測可能です。したがって、不変条件が尊重されることを保証する方が簡単です。
  • gotoステートメントを使用すると、その単純なフローを壊し、期待を破ります。たとえば、リソースを解放する必要があることに気付かない場合があります。
  • さまざまな場所にいる多くのgotoが、1つのgotoターゲットにあなたを送ることができます。したがって、この場所に到達したときの状態を確実に知ることは明らかではありません。したがって、誤った/根拠のない仮定を行うリスクは非常に大きくなります。

追加情報と引用:

Cは、無限に乱用可能なgotoステートメントと分岐先のラベルを提供します。正式にはgotoは必要ありません。実際には、それなしでコードを記述するのはほとんど常に簡単です。 (...)
それでも、gotoが場所を見つける可能性があるいくつかの状況を提案します。最も一般的な使用法は、2つのループを一度に抜けるなど、深くネストされた構造での処理を中止することです。 (...)
問題について独断的ではありませんが、gotoステートメントは、使用する場合は控えめに使用する必要があるようです

  • James Gosling &Henry McGiltonが彼らに書いた 1995 Java言語環境のホワイトペーパー

    以降のGotoステートメントはありません
    Javaにはgotoステートメントがありません。研究は、gotoが単に「そこにあるから」というよりも(誤って)使用されることを示しています。 gotoを排除することで言語が簡素化されました(...)Cコードの約100,000行の調査により、gotoステートメントの約90%が純粋にネストされたループから抜け出す効果を得るために使用されていることがわかりました。上記のように、マルチレベルのブレークアンドコンティニューは、gotoステートメントの必要性のほとんどを取り除きます。

  • Bjarne Stroustrup は、彼の 用語集 でgotoを定義しています:

    goto-悪名高いgoto。主にマシンで生成されたC++コードで役立ちます。

Gotoはいつ使用できますか?

K&Rのように、私は後藤について独断ではありません。後藤が人生を楽にしてくれる状況があることは認めます。

通常、Cでgotoを使用すると、マルチレベルのループ終了、またはこれまでに割り当てられたすべてのリソースを解放/ロック解除する適切な終了ポイントに到達する必要のあるエラー処理が可能になります(つまり、シーケンスでの複数の割り当ては複数のラベルを意味します)。 この記事 は、Linuxカーネルでのgotoのさまざまな使用法を数値化しています。

個人的にはそれを避けたいと思っており、Cの10年間で、最大10のgotoを使用しました。私はネストされたifsを使用することを好みます。これによりネストが深くなりすぎる場合は、関数を小さな部分に分解するか、カスケードでブールインジケーターを使用することを選択します。今日の最適化コンパイラーは、gotoを使用した同じコードとほぼ同じコードを生成するのに十分なほど巧妙です。

Gotoの使用は、言語に大きく依存します。

  • C++では、 [〜#〜] raii [〜#〜] を適切に使用すると、コンパイラーはスコープ外のオブジェクトを自動的に破棄するため、リソース/ロックはとにかくクリーンアップされ、 gotoの本当の必要性。

  • Java gotoは必要ありません(上記のJavaの作成者の引用とこれを参照してください 優れたスタックオーバーフローの回答 )):混乱を解消するガベージコレクターbreakcontinue、およびtry/catch例外処理は、gotoが役立つ可能性があるすべてのケースをカバーしますが、より安全で優れた方法です。Javaの人気gotoステートメントは現代の言語では回避できることを証明します。

有名なSSL goto fail脆弱性を拡大

重要な免責事項:コメントでの激しい議論を考慮して、 gotoステートメントがこのバグの唯一の原因であるふりをしないことを明確にしたいと思います。 gotoがなければバグが発生しないとは思いません。 gotoが深刻なバグに関与している可能性があることを示したいだけです。

プログラミングの歴史の中でgotoに関連する重大なバグがいくつあるかわかりません。詳細は伝えられないことがよくあります。しかし、iOSのセキュリティを弱める有名な Apple SSLバグ がありました。このバグの原因となったステートメントは、間違ったgotoステートメントでした。

バグの根本的な原因はそれ自体がgotoステートメントではなく、間違ったコピー/貼り付け、誤解を招くインデント、条件付きブロックの周りの波括弧の欠落、またはおそらく開発者の作業習慣であったと主張する人もいます。私もそれらを確認することはできません。これらの議論はすべて、おそらく仮説と解釈です。誰も知らない。 (一方、コメントで誰かが示唆したように間違ったマージの仮説は、同じ関数の他のインデントの不一致を考慮すると非常に良い候補のようです)。

唯一の客観的な事実は、重複したgotoが原因で関数が途中で終了してしまうことです。コードを見ると、同じ効果を引き起こす可能性があった他の唯一のステートメントは、リターンでした。

エラーは関数SSLEncodeSignedServerKeyExchange() in this file にあります:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

確かに、条件付きブロックを中括弧で囲むことでバグを防ぐことができました。
それは、コンパイル時の構文エラー(つまり修正)または冗長な無害なgotoのいずれかを引き起こしたでしょう。ちなみに、GCC 6では、オプションの警告により、一貫性のないインデントを検出して、これらのエラーを見つけることができます。

しかし、そもそもこれらのゴットはすべて、より構造化されたコードで回避できたはずです。したがって、gotoは少なくとも間接的にこのバグの原因です。それを回避できたはずの方法が少なくとも2つあります。

アプローチ1:if句またはネストされたifs

エラーの多くの条件を順番にテストする代わりに、問題が発生した場合にfailラベルに送信するたびに、暗号化操作をifステートメントで実行することを選択できます。間違った前提条件がなかった場合のみ:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

アプローチ2:エラーアキュムレータを使用する

このアプローチは、ここでのほとんどすべてのステートメントがerrエラーコードを設定する関数を呼び出し、errが0の場合にのみ残りのコードを実行する(つまり、関数が実行される)ことに基づいています。エラーなし)。安全で読みやすい素敵な代替手段は次のとおりです。

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

ここでは、単一のgotoはありません。失敗の出口点にすばやくジャンプするリスクはありません。視覚的には、位置がずれている行や忘れられたok &&を簡単に見つけることができます。

この構成はよりコンパクトです。これは、Cでは、論理and(&&)の2番目の部分は、最初の部分がtrueの場合にのみ評価されるという事実に基づいています。実際、最適化コンパイラによって生成されたアセンブラは、gotosを使用した元のコードとほぼ同等です。オプティマイザは条件のチェーンを非常によく検出してコードを生成します。最初の非nullの戻り値で最後にジャンプします( オンライン証明 )。

関数の最後に、テスト段階でokフラグとエラーコードの不一致を特定できる整合性チェックを想定することもできます。

assert( (ok==false && err!=0) || (ok==true && err==0) );

==0のような間違いは、誤って!=0に置き換えられるか、論理コネクタエラーがデバッグ段階で簡単に発見されます。

言ったように:私は代替構成がどんなバグも避けたであろうとふりをしません。私はそれらがバグをより発生しにくくしたかもしれないと言いたいだけです。

64
Christophe

有名なダイクストラの記事は、いくつかのプログラミング言語が実際に複数の入り口と出口ポイントを持つサブルーチンを作成できるときに書かれたものです。言い換えると、文字通り関数の真ん中に飛び込み、実際にその関数を呼び出したり、従来の方法で関数から戻ったりすることなく、関数内の任意の場所に飛び出します。これは、アセンブリ言語にも当てはまります。そのようなアプローチが、現在使用しているソフトウェアを構造化して記述する方法よりも優れていると主張する人はいません。

最近のほとんどのプログラミング言語では、関数は1つの入口と1つの出口点で非常に明確に定義されています。入口点は、関数のパラメーターを指定する場所で、それを呼び出すと、出口点は結果の値を返し、元の関数呼び出しに続く命令で実行を継続します。

その機能の中で、理性の範囲内で、あなたが望むことは何でもできるはずです。関数にgotoや2つを置くと、それが明確になったり速度が向上したりする場合は、どうですか?関数の全体の要点は、明確に定義された機能を少し隔離することです。これにより、内部でどのように機能するかを考える必要がなくなります。いったん記述したら、それを使用するだけです。

そして、はい、関数内に複数のreturnステートメントを含めることができます。適切な関数には、常に1つの場所から戻ります(基本的には関数の裏側)。これは、関数が適切に戻る前にgotoで関数からジャンプすることとはまったく同じではありません。

enter image description here

つまり、gotoを使用することではありません。それは虐待を避けることです。ゴトを使用してひどく複雑なプログラムを作成できることは誰もが同意しますが、関数を悪用することでも同じことができます(ゴトを悪用するほうがはるかに簡単です)。

それだけの価値があるので、行番号スタイルのBASICプログラムからPascalおよび中かっこ言語を使用した構造化プログラミングに卒業して以来、私はgotoを使用する必要がありませんでした。 (ループからのマルチレベルの早期終了をサポートしていない言語で)ネストされたループから早期終了するのが唯一の誘惑ですが、通常はよりクリーンな別の方法を見つけることができます。

34
Robert Harvey

「goto」ステートメントはどのようなバグを引き起こしますか?歴史的に重要な例はありますか?

Forおよびwhileループを取得する簡単な方法として、BASICプログラムを子として書くときにgotoステートメントを使用していました(コモドール64 BASICにはwhileループがなく、適切な構文を学ぶには未熟すぎましたおよびforループの使用方法)。私のコードは頻繁に取るに足らないものでしたが、ループのバグはすぐにgotoを使用したことが原因である可能性があります。

今は主にPythonを使用しています。Pythonは、gotoを使用する必要がないと判断した高水準プログラミング言語です。

Edsger Dijkstraが1968年に「Gotoは有害であると見なした」と宣言したとき、関連するバグがgotoのせいになる可能性がある少数の例を挙げず、むしろgoto高水準言語では不要)と宣言しました であり、今日の通常の制御フローであるループと条件文を考慮して、回避する必要があります。彼の言葉:

go toを無制限に使用すると、プロセスの進行状況を記述するための意味のある座標のセットを見つけるのが非常に困難になるという直接的な影響があります。
[...]
現状のgo toステートメントは原始的すぎます。自分のプログラムを台無しにするのは、あまりにも多くの誘いです。

おそらく、gotoを含むコードをデバッグするたびに、山ほどのバグの例がありました。しかし、彼の論文は、gotoが高水準言語には不要であるという証明に裏付けられた、一般化された立場声明でした。一般的なバグは、問題のコードを静的に分析する機能がない場合があることです。

コードをgotoステートメントで静的に分析するのは非常に困難です。特に、制御フロー(以前はこれを使用していた)に戻ったり、コードの無関係なセクションに戻ったりした場合はなおさらです。コードはこの方法で作成できます。これは、非常に少ないコンピューティングリソース、つまりマシンのアーキテクチャを高度に最適化するために行われました。

外典の例

コードの性質上、メンテナが非常にエレガントに最適化されているだけでなく 彼が「修正」することは不可能 であることがわかったブラックジャックプログラムがありました。それは、gotoに大きく依存しているマシンコードでプログラムされたので、この話は非常に関連があると思います。これは私が考えることができる最良の標準的な例です。

反例

ただし、CPythonのCソース(最も一般的で参照Python実装))はgotoステートメントを使用して大きな効果をもたらします。これらは、関数内の無関係な制御フローをバイパスして関数の終わり、読みやすさを失わずに関数をより効率的にするこれは、関数の単一の出口点を持つという 構造化プログラミング -の理想の1つを尊重します。

記録として、この反例は静的に分析するのが非常に簡単であることがわかります。

11
Aaron Hall

wigwam が現在の建築芸術であったとき、彼らのビルダーは間違いなくウィグワムの構築、煙を逃がす方法などについて健全な実用的なアドバイスを与えることができました。幸いなことに、今日のビルダーはおそらくそのアドバイスのほとんどを忘れることができます。

stagecoach が現在の交通手段であったとき、彼らのドライバーは間違いなく、ステージコーチの馬、 highwaymen、 から身を守る方法などについて実用的なアドバイスを与えることができます。幸いなことに、今日の運転手もそのアドバイスのほとんどを忘れることができます。

パンチされたカード が現在のプログラミングアートである場合、彼らの開業医は同様に、カードの編成、ステートメントの番号付けの方法などについて健全で実用的なアドバイスを与えることができます。そのアドバイスが今日非常に関連性があるかどうかはわかりません。

"to numberステートメント" というフレーズを知っていても十分な年齢ですか?あなたはそれを知る必要はありませんが、もしあなたが知らなければ、gotoに対する警告が主に関連していた歴史的文脈に慣れていません。

gotoに対する警告は、今日はあまり重要ではありません。 while/forループと関数呼び出しの基本的なトレーニングでは、gotoを頻繁に発行することを考えることさえしません。考えてみれば、理由はあると思いますので、どうぞ。

しかし、gotoステートメントを悪用することはできませんか?

回答:もちろん、乱用される可能性がありますが、その乱用は、定数が機能する変数の使用やカットアンドペーストプログラミング(それ以外の場合)などのはるかに一般的な間違いと比較して、ソフトウェアエンジニアリングにおけるかなり小さな問題です。リファクタリングの無視として知られています)。あなたが多くの危険にさらされているとは思えません。 longjmpを使用している場合や、他の方法で制御を遠くのコードに転送している場合を除き、gotoを使用する場合、または楽しみながら試してみたい場合は、先に進んでください。大丈夫だよ。

gotoが悪者を演じる最近のホラーストーリーがないことに気付くでしょう。これらの物語のほとんどまたはすべてが30歳または40歳のようです。それらの物語がほとんど時代遅れであると考えるならば、あなたは堅実な立場に立っています。

11
thb

gotoステートメントを使用して、他の優れた答えに1つのことを追加するには、プログラム内の特定の場所に到達した方法を正確に伝えるのが難しい場合があります。特定の行で例外が発生したことがわかりますが、コードにgotoがある場合、プログラム全体を検索せずに、どのステートメントが実行されてこの例外の原因となる状態になるのかを知る方法はありません。呼び出しスタックも視覚的フローもありません。例外を発生させた行に対してgotoを実行した状態が悪い状態になる1000行先のステートメントがある可能性があります。

7
qfwfq

gotoは、人間が他の形式のフロー制御よりも推論するのが難しいです。

正しいコードのプログラミングは難しいです。正しいプログラムを書くのは難しい、プログラムが正しいかどうかを判断するのは難しい、プログラムが正しいことを証明するのは難しい。

必要なことを漠然と実行するコードを取得することは、プログラミングに関する他のすべてのものに比べて簡単です。

gotoは、必要なことを行うコードを取得して、いくつかのプログラムを解決します。代わりの方法がしばしばそうである間、それは正しさのチェックを簡単にするのを助けません。

Gotoが適切なソリューションであるプログラミングのスタイルがあります。その邪悪な双子、comefromが適切な解決策であるプログラミングのスタイルさえあります。これらのどちらの場合でも、理解したパターンで使用していることを確認するために細心の注意を払う必要があり、正確さを保証するために困難なことをしていないことを手動で確認する必要があります。

例として、コルーチンと呼ばれるいくつかの言語の機能があります。コルーチンはスレッドレススレッドです。実行するスレッドのない実行状態。あなたは彼らに実行するように頼むことができます、そして彼らは彼ら自身の一部を実行し、それから彼ら自身を一時停止し、フロー制御を引き渡すことができます。

コルーチンをサポートしない言語の「浅い」コルーチン(C++ pre-C++ 20およびCなど)は、gotoと手動の状態管理を組み合わせて使用​​することで可能になります。 「深い」コルーチンは、Cのsetjmp関数とlongjmp関数を使用して実行できます。

コルーチンが非常に有用であり、手作業で慎重に記述する価値がある場合があります。

C++の場合、それらはそれらをサポートするために言語を拡張しているほど十分に有用であることがわかっています。 gotosと手動の状態管理は、ゼロコストの抽象化レイヤーの背後に隠されているため、プログラマーはそれらを記述できますwithout gotoの混乱を証明する必要があることの難しさvoid**s、手動での構築/状態の破壊などが正しい。

Gotoは、whileまたはforまたはifまたはswitchなどの上位レベルの抽象化の背後に隠されます。これらの高レベルの抽象化は、正しいことを証明し、チェックするのが簡単です。

言語がmissingだった場合(一部の現代の言語にはコルーチンがないなど)、問題に適合しないパターンと一緒に簡単に使用するか、gotoを使用すると、代わりになります。

一般的なケースでコンピュータに何をしたいかを漠然と実行させるのはeasyです。確実に堅牢なコードを書くことはhardです。後藤は、2番目よりも1番目の方がはるかに役立ちます。それは、バグを追跡するのが非常に難しく、表面的に「機能している」コードの兆候であるため、「gotoは有害であると見なされました」。十分な努力を払っても、「goto」を使用してコードを確実に堅牢にすることができるため、絶対ルールを保持することは間違っています。経験則として、それは良いものです。

7
Yakk

http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html からこのコードを見てください。大規模な囚人のジレンマシミュレーション。古いFORTRANまたはBASICコードを見たことがあれば、それはそれほど珍しいことではないことに気付くでしょう。

C  Not Nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

ここには、GOTOステートメントをはるかに超える多くの問題があります。正直に言うと、GOTOステートメントは少しスケープゴートだったと思います。しかし、ここでは制御フローが完全に明確ではなく、コードは、何が起こっているのか非常に不明確になるような方法で混在しています。コメントを追加したり、より適切な変数名を使用したりせずに、これをGOTOなしのブロック構造に変更すると、読みやすく、わかりやすくなります。

6
prosfilaes

gotoは、通常は不要な場所で使用されるため危険です。不要なものを使用するのは危険ですが、特にgotoを使用します。グーグルでgotoが原因で多くのエラーが見つかる場合、これはそれ自体を使用しない理由ではありません(プログラミングに固有であるため、言語機能を使用すると常にバグが発生します)が、それらのいくつかは明らかに非常に関連していますtogotoの使用法。


使用/使用しない理由goto

  • ループが必要な場合は、whileまたはforを使用する必要があります。

  • 条件付きジャンプを行う必要がある場合は、if/then/else

  • 手順が必要な場合は、関数/メソッドを呼び出します。

  • 関数を終了する必要がある場合は、returnだけです。

gotoが使用され、適切に使用されているのを目にした場所を数えることができます。

  • CPython
  • libKTX
  • おそらくもう少し

LibKTXには、次のコードを持つ関数があります

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

言語がCであるため、ここではgotoが便利です。

  • 私たちは関数の中にいます
  • クリーンアップ関数を作成することはできません(別のスコープに入るので、アクセス可能な呼び出し元関数の状態を作成する方が負担が大きいため)。

Cにはクラスがないため、この使用例は便利です。クリーンアップする最も簡単な方法は、gotoを使用することです。

C++で同じコードを使用している場合、gotoは不要です。

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

それはどのようなバグを引き起こす可能性がありますか?何か。無限ループ、間違った実行順序、スタックスクリュー.

文書化されたケースは、SSLの脆弱性であり、不正使用によるMan in the Middle攻撃を可能にしましたgoto: 記事はこちら

これはタイプミスですが、しばらくの間気付かれることはありませんでした。コードが別の方法で構成されている場合、適切にテストすると、そのようなエラーは不可能でした。

0
CoffeDeveloper

保守可能なプログラミングの原則の1つはカプセル化です。重要なのは、定義されたインターフェースを使用してモジュール/ルーチン/サブルーチン/コンポーネント/オブジェクトとインターフェースし、そのインターフェースのみを使用することであり、結果は予測可能です(ユニットが効果的にテストされている場合)。

コードの単一ユニット内でも、同じ原則が適用されます。構造化プログラミングまたはオブジェクト指向プログラミングの原則を一貫して適用する場合、次のことは行われません。

  1. コードを通じて予期しないパスを作成する
  2. 未定義または許容されない変数値を含むコードのセクションに到着する
  3. 未定義または許容されない変数値でコードのパスを終了する
  4. トランザクションユニットを完了できませんでした
  5. コードまたはデータの一部をメモリに残しますが、実際には無効ですが、ガベージクリーンアップおよび再割り当ての対象にはなりません。コード制御パスがユニットを終了するときに、解放を呼び出す明示的な構成を使用して解放のシグナルを送っていないためです。

これらの処理エラーのより一般的な症状には、メモリリーク、メモリの滞留、ポインタのオーバーフロー、クラッシュ、不完全なデータレコード、レコードの追加/変更/削除の例外、メモリページ違反などがあります。

これらの問題のユーザーが観察できる兆候には、ユーザーインターフェイスのロックアップ、パフォーマンスの段階的な低下、不完全なデータレコード、トランザクションの開始または完了不能、破損したデータ、ネットワークの停止、停電、組み込みシステムの障害(ミサイルの制御不能による)があります。航空管制システムでの追跡および制御機能の喪失、タイマーの障害などに。カタログはvery豊富です。

0
jaxter