「goto」ステートメントはどのようなバグにつながりますか?歴史的に重要な例はありますか?


103

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

XKCD 代替テキスト: 「ニール・スティーブンソンは自分のラベルに「dengo」という名前を付けるのはかわいいと思っている」
オリジナルのコミックを参照:http : //xkcd.com/292/

私はこれを早く学んだからです。どんな種類のバグがgoto実際につながるのかについての洞察や経験は本当にありません。ここで何を話しているのか:

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

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


33
このトピックに関するダイクストラの元の短い論文を読んだことがありますか?(それははい/いいえの質問であり、明確な瞬間「はい」以外の答えは「いいえ」です。)
ジョンR.ストローム

2
何か重大な障害が発生した場合にアラームが終了すると推測します。決して戻らない場所。電源障害のように、デバイスまたはファイルが消えます。いくつかの種類の「on error goto」...しかし、ほとんどの「ほぼ壊滅的な」エラーは「try-catch」コンストラクトで処理できると思います。
レンネ

13
計算されたGOTOについてはまだ誰も言及していません。地球が若い頃、BASICには行番号がありました。次のように書くことができます:100 N = A * 100; GOTO N.デバッグしてみてください:)
mcottle

7
@ JohnR.Strohmダイクストラの論文を読みました。1968年に書かれ、スイッチステートメントや関数などの高度な機能を含む言語では、gotoの必要性を減らすことができることを示唆しています。gotoがフロー制御の主要な方法である言語に対応して書かれており、プログラム内の任意のポイントにジャンプするために使用できます。Cなどのこのホワイトペーパーの作成後に開発された言語では、gotoは同じスタックフレーム内の場所にしかジャンプできず、一般に他のオプションがうまく機能しない場合にのみ使用されます。(続き...)
レイ

10
(...続き)彼のポイントはその時点で有効でしたが、gotoが良いアイデアであるかどうかに関係なく、もはや関係ありません。何らかの方法で議論したいのであれば、現代の言語に関連する議論をしなければなりません。ところで、Knuthの「gotoステートメントを使用した構造化プログラミング」読んでいますか?
レイ

回答:


2

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

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

gotoは、ロジックの範囲をプログラム全体に効果的に拡張します。これは、ほんの数行にまたがる最小のプログラムを除いて、必ずあなたの脳を打ち負かすでしょう。

ですから、あなたがもう少し間違えたかどうかを知ることはできません。なぜなら、あなたのかわいそうな小さな頭を取り込んでチェックすることが多すぎるからです。これは本当の問題であり、バグはおそらくありそうな結果です。


非ローカルgoto?
デデュプリケーター

thebrain.mcgill.ca/flash/capsules/experience_jaune03.html <<人間の心は一度に平均7つのことを追跡できます。スコープを拡大すると、追跡する必要のあるものがさらに追加されるため、理論的根拠はこの制限に影響します。したがって、導入されるバグのタイプは、おそらく省略と忘却のタイプです。また、「スコープを厳しく保つ」という一般的な緩和戦略とグッドプラクティスも提供します。したがって、私はこの答えを受け入れます。よくやったマーティン。
アキバ

1
なんてかっこいい?!200以上の票が投じられ、わずか1票で勝ちました!
マーティンマート

68

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

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

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

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

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

コード検証がunrestrictedを排除する方法の例として、gotoずっと前に、動的に変化するユーザーインターフェイスに役立つ制御構造につまずきました。私はそれを差分実行と呼びました。それは完全にチューリング普遍的であるが、その正しさの証明は、純粋な構造化プログラミングに依存-いいえgotoreturncontinuebreak、または例外。

ここに画像の説明を入力してください


49
構造化プログラミングを使用して記述した場合でも、最も些細なプログラムを除き、正確性を証明することはできません。自信を高めることができるだけです。構造化プログラミングはそれを実現します。
ロバートハーベイ

23
@MikeDunlavey:関連:「上記のコードのバグに注意してください。私はそれが正しいことを証明しただけで、試したことはありません。」staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Mooing Duck

26
@RobertHarvey 正当性の証明が些細なプログラムに対してのみ実用的であるというのは全く真実はありません。ただし、構造化プログラミングよりも特別なツールが必要です。
マイクハスケル

18
@MSalters:それは研究プロジェクトです。このような研究プロジェクトの目標は、リソースがあれば検証済みのコンパイラを作成できることを示すことです。彼らの現在の研究を見れば、彼らは単にCの新しいバージョンをサポートすることに興味がなく、むしろ証明できる正確性のプロパティを拡張することに興味があることがわかります。そのため、C90、C99、C11、Pascal、Fortran、Algol、またはPlankalkülをサポートしているかどうかはまったく関係ありません。
ヨルグWミットタグ

8
@martineau:私はそれらの「ボソンフレーズ」に不満だ。プログラマーはそれが悪いか良いことに同意するのだ。物事にはもっと基本的な理由が必要です。
マイクダンラベイ

63

なぜgotoは危険ですか?

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

スパゲッティコードを回避できたとしても、ごく少数のgotoを使用するだけでも、次のようなバグやリソースリークが発生しやすくなります。

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

追加情報と引用符:

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

  • James GoslingとHenry McGiltonは、1995年のJava言語環境のホワイトペーパーで次のように書いています

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

  • Bjarne Stroustrupは、用語集でgotoを次の魅力的な用語で定義しています。

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

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

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

通常、Cでは、gotoを使用すると、マルチレベルループ出口、またはこれまでに割り当てられたすべてのリソースを解放/ロック解除する適切な出口点に到達する必要があるエラー処理が可能になります。 この記事では、Linuxカーネルでのgotoのさまざまな使用法を定量化します。

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

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

  • C ++では、RAIIを適切に使用すると、コンパイラがスコープ外のオブジェクトを自動的に破棄するため、リソース/ロックがとにかくクリーンになり、gotoの実際の必要がなくなります。

  • Javaではgotoの必要はありません(上記のJavaの著者の引用とこの優れたStack Overflowの回答を参照):混乱、、、および/ 例外処理をクリーンにするガベージコレクターはbreak、役立つ可能性のあるすべてのケースをカバーしますが、より安全で優れていますマナー。Javaの人気は、現代の言語ではgotoステートメントを回避できることを証明しています。continuetrycatchgoto

有名なSSL goto失敗の脆弱性を拡大

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

gotoプログラミングの歴史の中で、いくつの深刻なバグが関連しているのかはわかりません。詳細はしばしば伝えられません。しかし、iOSのセキュリティを弱める有名なApple SSLバグがありました。このバグにつながったgoto声明は間違った声明でした。

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

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

このファイルのエラーは機能SSLEncodeSignedServerKeyExchange()ています

    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はすべて、より構造化されたコードで回避できたはずです。したがって、gotoは少なくとも間接的にこのバグの原因です。それを回避できた少なくとも2つの異なる方法があります。

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

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

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

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

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

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

ここには、1つのgotoはありません。障害の出口ポイントにすばやくジャンプするリスクはありません。そして、視覚的には、ずれている線や忘れられてok &&いる線を見つけるのは簡単でしょう。

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

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

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

==0誤って、!=0または論理コネクタエラーで置き換えられたなどの間違いは、デバッグフェーズで簡単に発見されます。

前述のように、代替の構成体がバグを回避したとは思わない。バグの発生をより困難にすることができたと言いたいだけです。


34
いいえ、Apple goto failバグは、if文の後に中括弧を含めないことを賢いプログラマーが決定したことが原因です。:-)そのため、次のメンテナンスプログラマ(または同じもの、同じ違い)が来て、ifステートメントがtrueと評価された場合にのみ実行されるはずの別のコード行を追加すると、ステートメントは毎回実行されました。Bam、「goto fail」バグ。これは主要なセキュリティバグでした。しかし、gotoステートメントが使用されたという事実とは本質的に何の関係もありませんでした。
クレイグ

47
Appleの「goto fail」バグは、コードの行が重複していることが直接の原因でした。そのバグの特定の影響はその行がgoto波括弧のないifステートメントの中にあることによって引き起こされました。しかし、回避gotoすることで、コードがランダムに複製された行の影響を受けないようにすることを提案している場合は、悪い知らせがあります。
イミビス

12
ショートカットブール演算子の動作を使用して、副作用を介してステートメントを実行し、それがステートメントよりも明確であると主張していifますか?楽しい時間。:)
クレイグ

38
「goto fail」の代わりに「failed = true」というステートメントがあった場合、まったく同じことが起こります。
gnasher729

15
そのバグは、ずさんな開発者が自分がやっていることに注意を払わず、独自のコード変更を読んでいないことが原因でした。これ以上でもそれ以下でもありません。さて、多分そこにいくつかのテストの見落としを投げ入れてください。
軌道上の明るさのレース

34

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

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

その機能内では、理にかなって、あなたが望むものは何でもできるはずです。関数にgotoを1つまたは2つ追加すると、機能がより明確になり、速度が向上します。関数の全体的なポイントは、明確に定義された機能を少し隔離することです。これにより、内部でどのように機能するかを考える必要がなくなります。作成したら、それを使用するだけです。

はい、関数内に複数のreturnステートメントを含めることができます。戻る元の適切な関数には常に1つの場所があります(基本的には関数の裏側)。これは、関数が適切に戻る前にgotoを使用して関数から飛び出すこととはまったく異なります。

ここに画像の説明を入力してください

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

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


2
コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
maple_shaft

11

「goto」ステートメントはどのようなバグにつながりますか?歴史的に重要な例はありますか?

gotoforおよびwhileループを取得する簡単な方法として、BASICプログラムを子として記述するときにステートメントを使用していました(Commodore 64 BASICにはwhileループがなく、forループの適切な構文と使用法を学ぶには未熟すぎました)。私のコードは頻繁に些細なものでしたが、ループのバグはgotoの使用に起因するものでした。

現在、主にPythonを使用しています。Pythonは、gotoの必要がないと判断した高レベルのプログラミング言語です。

Edsger Dijkstraは、1968年に「後藤は有害だ」と宣言したときに、関連するバグをgotoで非難できるいくつかの例を挙げませんでした。むしろ、より高いレベルの言語にgoto不要であり、今日の通常の制御フローであるループと条件を考慮します。彼の言葉:

go toの自由な使用は、プロセスの進行状況を記述する意味のある座標のセットを見つけることが非常に困難になるという即時の結果をもたらします。
[...] に行く、それはあまりにも原始的である現状のまま声明。自分のプログラムを台無しにするのはあまりにも招待です。

彼はおそらく、コードをデバッグするたびにたくさんのバグの例を持っていたでしょうgoto。しかし、彼の論文は、gotoより高いレベルの言語には不要な証明によって裏付けられた一般化された立場表明でした。一般的なバグは、問題のコードを静的に分析できない可能性があることです。

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

外典の例

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

反例

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

記録的には、この反例を静的に分析するのは非常に簡単だと思います。


5
私が遭遇した「メルの物語」は、(1)操作後にすべての機械語命令がgotoを実行し、(2)連続したメモリ位置に一連の命令を配置するのは非常に非効率的である奇妙なアーキテクチャでした。また、プログラムは、コンパイルされた言語ではなく、手動で最適化されたアセンブリで作成されました。

@MilesRoutあなたはここで正しいかもしれませんが、構造化プログラミングに関するウィキペディアのページは次のように述べています。 、構造化プログラミングで必要な単一の出口点の代わりに。」-あなたはこれが間違っていると言っていますか?引用する情報源はありますか?
アーロンホール

@AaronHallそれは本来の意味ではありません。
マイルルーティング

11

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

駅馬車が現在の輸送芸術であったとき、彼らの運転手は、馬車馬、ハイウェイマンに対する防御方法などに関する実用的なアドバイスを間違いなく与えることができました。幸いなことに、今日のドライバーはそのアドバイスの大部分を忘れることができます。

ときパンチカードは、現在のプログラミングの芸術だった、彼らの専門家は同様にあなたがそうでステートメントを数、および方法を、カードの組織化に関する実用的なアドバイスを鳴らす与えることができます。そのアドバイスが今日非常に適切かどうかはわかりません。

「文に番号を付けるというフレーズを知っていても、あなたは十分に古いですか?あなたはそれを知る必要はありませんが、もし知らなければ、あなたは警告gotoが主に関連していた歴史的文脈に精通していません。

に対する警告gotoは、今日ではあまり意味がない。while / forループおよび関数呼び出しの基本的なトレーニングでは、非常に頻繁に発行することすら考えないでしょうgoto。考えてみると、おそらく理由があるので、先に進んでください。

しかし、goto声明を悪用することはできませんか?

回答:もちろん、悪用される可能性はありますが、その悪用は、定数が役立つ変数を使用したり、カットアンドペーストプログラミング(そうでなければ、リファクタリングの怠慢として知られています)。私はあなたが多くの危険にさらされていることを疑います。使用している、longjmpまたは他の方法で制御を遠方のコードに移している場合を除き、aを使用することを考えている場合、gotoまたは単に試してみたい場合は、先に進んでください。大丈夫です。

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


1
少なくとも1つの下票を集めたことがわかります。まあ、それは予想されることです。さらなる投票が来るかもしれません。しかし、数十年のプログラミング経験から、この例では従来の答えが間違っていることを教えられたときに、従来の答えをするつもりはありませんとにかく、これは私の見解です。理由を挙げました。読者が決めることができます。
16年

1
あなたの投稿はあまりにも悪いのでコメントを下すことができません。
ピーターB

7

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


1
Visual Basic 6とVBAにはエラー処理が苦痛がありますが、を使用するとOn Error Goto errhResume再試行、Resume Next問題のある行のスキップ、Erlどの行であったかを知ることができます(行番号を使用する場合)。
シーズティマーマン

@CeesTimmerman私はVisual Basicをほとんど使っていませんが、それは知りませんでした。それについてのコメントを追加します。
qfwfq

2
@CeesTimmerman:独自のオンエラーブロックを持たないサブルーチン内でエラーが発生した場合、「再開」のVB6セマンティクスは恐ろしいです。 Resumeその関数を最初から再実行し、Resume Nextまだ関数にないすべてのものをスキップします。
-supercat

2
@supercat私が言ったように、それは痛いです。正確な行を知る必要がある場合は、それらすべてに番号を付けるか、エラー処理を一時的に無効にしOn Error Goto 0て手動でデバッグする必要があります。Resume確認Err.Numberして必要な調整を行った後に使用することを意図しています。
シーズティマーマン

1
現代の言語では、ラベル付きループgotoのみ機能するラベル付きラインでのみ機能することに注意してください。
シーズティマーマン

7

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

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

コードを漠然とやりたいようにさせることは、プログラミングに関する他のすべてに比べて簡単です。

gotoは、必要な処理を行うコードを取得することで、いくつかのプログラムを解決します。正しいかどうかを確認するのは簡単ではありませんが、多くの場合は代替手段があります。

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

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

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

コルーチンが非常に有用であるため、手作業で注意深く作成する価値がある場合があります。

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

gotoは、whileor foriforのような高レベルの抽象化の背後に隠れswitchます。これらの高レベルの抽象化は、正しいことを確認して確認するのが簡単です。

言語にそれらのいくつかが欠けていた場合(一部の現代言語がコルーチンを欠いているなど)、問題に合わないパターンに沿ってつぶやくか、gotoを使用することが代替手段になります。

よくあるケースでコンピュータに望んでいることを漠然とさせるのは簡単です。信頼性の高い堅牢なコードを記述することは困難です。後藤は、2番目に役立つよりも最初に役立つことが多いです。したがって、「gotoは有害と見なされます」。これは、バグを深く追跡するのが難しい表面的に「動作する」コードの兆候であるためです。十分な努力をすれば、「goto」を使用してコードを確実に堅牢にすることができます。そのため、絶対ルールを保持することは間違っています。しかし、経験則としては良いものです。


1
longjmpC setjmpでは、親関数でスタックをa に戻すためにのみ有効です。(複数レベルのネストから抜け出すようなものですが、ループではなく関数呼び出し用です)。 関数内longjjmpsetjmpdone からコンテキストへの戻りは、それ以降は無効です(そして、ほとんどの場合、実際には機能しないと思われます)。私はコルーチンに精通していませんが、ユーザースペーススレッドに似たコルーチンの説明から、それは独自のスタックと保存されたレジスタコンテキストlongjmpを必要とし、登録のみを提供します-個別のスタックではなく保存。
ピーター

1
ああ、私はただgoogledする必要があります:fanf.livejournal.com/105413.htmlはコルーチンが実行するためのスタックスペースの作成について説明しています。(私が疑ったように、setjmp / longjmpを使用するだけでなく必要です)。
ピーターコーデス

2
@PeterCordes:コルーチンとして使用するのに適したペアjmp_buffをコードで作成できる手段を提供するための実装は必要ありません。一部の実装では、標準ではそのような機能を提供することを要求していなくても、コードがコードを作成する手段を指定しています。多くの場合、jmp_buffは不透明な構造であり、異なるコンパイラ間で一貫して動作することが保証されないため、「標準C」などの手法に依存するコードについては説明しません。
supercat

6

実際に大規模な囚人のジレンマシミュレーションの一部であった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のないブロック構造に変更すると、読みやすく、追跡しやすくなります。


4
本当です:-)言及する価値があるかもしれません:レガシーFORTANとBASICにはブロック構造がありませんでした。
クリストフ

数字の代わりにテキストラベルを使用するか、少なくとも番号付きの行にコメントを付けると非常に役立ちます。
シーズティマーマン

FORTRAN-IVの時代に戻りました。GOTOの使用を制限して、IF / ELSE IF / ELSEブロック、WHILEループ、およびBREAKとNEXTをループに実装しました。誰かが私にスパゲッティコードの悪さと、GOTOでブロック構造を実装しなければならない場合でも、それを避けるために構造化する必要がある理由を教えてくれました。その後、プリプロセッサ(RATFOR、IIRC)の使用を開始しました。後藤は本質的に悪ではなく、アセンブラーの分岐命令にマップする、非常に強力な低レベルの構成体です。言語が不足しているために使用する必要がある場合は、適切なブロック構造を構築または拡張するために使用します。
nigel222

@CeesTimmerman:それは園芸品種のFORTRAN IVコードです。テキストラベルはFORTRAN IVではサポートされていません。言語の最新バージョンがそれらをサポートしているかどうかはわかりません。コードと同じ行に対するコメントは、標準のFORTRAN IVではサポートされていませんでしたが、それらをサポートするベンダー固有の拡張機能があった可能性があります。「現在の」FORTRAN標準が何を言っているのかわかりません。私はずっと前にFORTRANを放棄しましたが、見逃していません。(私が見た最新のFORTRANコードは、1970年代初期のPASCALと非常によく似ています。)
ジョンR.ストローム

1
@CeesTimmerman:ベンダー固有の拡張。一般的に、コードは本当にコメントに恥ずかしがり屋です。また、後から行を追加しやすくするために、CONTINUEステートメントとFORMATステートメントにのみ行番号を付ける慣習があります。このコードはその規則に従っていません。
ジョンR.ストローム

0

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

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

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

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

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


3
goto一度も言及せずに答えました。:)
ロバートハーベイ

0

gotoは通常、必要でない場所で使用されるため、危険です。必要のないものを使用するのは危険ですが、特に後藤には注意してください。Googleを使用すると、gotoに起因する多くのエラーが見つかりますが、これはそれを使用しない理由ではありません(言語機能を使用すると、バグは常に発生しますが、それはプログラミングに内在するためです)gotoの使用。


gotoを使用する/使用しない理由:

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

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

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

  • あなただけの、機能を終了する必要がある場合return

gotoを使って、適切に使用され使用されているのを見てきました。

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

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

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

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

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

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(){

    }



}

どのようなバグにつながる可能性がありますか?何でも。無限ループ、実行順序の誤り、スタックスクリュー..

文書化されたケースは、gotoの誤った使用によって引き起こされる中間者攻撃を可能にするSSLの脆弱性です。ここに記事があります

これはタイプミスですが、しばらく気付かなかったため、コードが他の方法で構造化されていれば、そのようなエラーは適切にテストできませんでした。


2
「goto」の使用はSSLバグとまったく無関係であると非常に明確に説明する1つの答えに対するコメントがたくさんあります。
gnasher729

ええ、私は承諾する前にgotoバグのより良い歴史的な例を待っています。言われるように; 実際、そのコード行に何があっても問題ありません。中かっこがないと、それを実行し、エラーが発生します。しかし、私は興味があります。「スタックねじ」とは何ですか?
アキバ

1
PL / 1 CICSで以前に取り組んだコードの例。プログラムは、単一行マップで構成される画面を出力します。画面が返されると、画面が送信したマップの配列が見つかりました。その要素を使用して1つの配列のインデックスを検索し、そのインデックスを配列変数のインデックスに使用してGOTOを実行し、個々のマップを処理できるようにしました。送信されたマップの配列が正しくない(または破損していた)場合、間違ったラベルに対してGOTOを実行しようとしたり、ラベル変数の配列の後に要素にアクセスしたためにさらにクラッシュしたりします。ケースタイプ構文を使用するように書き直されました。
キックスタート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.