
コンピュータプログラミングにおいて、gotoは制御フロー 文であり、制御をソースコードの別の行に渡します。関数呼び出しは呼び出し元への戻りをサポートしますが、goto は戻りをサポートしません。goto 文の表記はプログラミング言語によって異なります。小文字 ( goto)、大文字 ( GOTO)、大文字小文字を区別しないなど、また、1つの単語または2つの単語 ( などGO TO) で表す場合もあります。
goto文は、主に機械語 のジャンプ命令(分岐または転送)へのアクセスを提供するために言語に組み込まれていますが、ジャンプセマンティクスの使用に伴う潜在的な問題のため、言語は時間の経過とともに、goto文の必要性と使用を置き換えることを目的とした他のフロー制御メカニズムを拡張してきました。多くの現代の言語にはgoto文が全く含まれていません。goto文を含む多くの言語は、使用によって発生する可能性のある問題を制限するために、goto文の使用を制限しています。さらに、goto文の使用は一般的に不適切な選択肢と見なされているため、ソフトウェア開発者は、goto文を提供する言語を使用している場合でも、goto文の使用を避ける傾向があります。
一般的に、goto の使用は、より構造化されたフロー制御を使用するコードよりも、認知負荷が高くバグが多いコードにつながるため、あまり良い選択ではないと考えられています。goto の使用はコンピュータの黎明期には一般的でしたが、1960年代から1970年代にかけて、goto をより構造化されたフロー制御に置き換えることを目指した構造化プログラミング運動の協調的な取り組みにより、その使用は大幅に減少しました。とはいえ、goto は現在でも使用されていますが、一般的に特定のシナリオに限定されています。
構造化プログラム定理は、フローチャートとして表現できるプログラムを書くのにgoto文は必要ないことを実証した。コードの重複や追加の変数が必要になる可能性があるという注意点があるものの、シーケンス、選択、反復という3つのプログラミング構造の組み合わせで、チューリングマシンで実行できるあらゆる計算を実行できる。[1]
gotoのサポートは言語によって異なります。古く設計された言語は無制限のサポートを提供する傾向があり、新しい言語は制限付き、あるいは全くサポートしない傾向があります。例えば、C言語では別の関数のラベルへのジャンプは許可されていません。[2] [3]
C#とVisual Basic .NETはどちらもgotoをサポートしています。[4] [5]しかし、これらの言語では、包含スコープ外のラベルへのジャンプは許可されておらず、オブジェクトの破棄とfinally構文が尊重されるため、他の言語のgotoに比べて大幅に強力で危険性も低いものとなっています。また、これらの言語では、スコープが囲むswitch文caseとなるラベルとdefaultキーワードが作成されます。また、暗黙的なフォールスルーの代わりに、
またはが使用されますが、これは許可されていません。goto casegoto default
多くの言語にはgoto文のセマンティクスが欠けています。Javaには予約語が ありますが、コンパイルされたファイルはgoto文とラベル文を生成しますgotoが、実際には使用できません。 [説明が必要] [6] Pythonはgoto文をサポートしていませんが、それを提供するジョークモジュールがいくつかあります。[7] [8] PHPはバージョン5.3までgoto文をサポートしていませんでした。[9].class
PL/Iには、ブロック外転送のスタックをアンワインドし、ブロック内への転送を許可しない goto ステートメントがあります。
gotoセマンティクス文を持つ言語のほとんどはキーワードgotoを使用しますが、特に古い言語では他の構文も使用されます。例えば、MADでは[ 10]、APLでは右向きの矢印 を使用します。
TRANSFER TO→
Scheme などの関数型プログラミング言語では、通常、goto は使用されず、代わりに継続が使用されます。
Fortranでは、計算型gotoは式の値に基づいてリスト内の複数のラベルのいずれかにジャンプします。例としては、がありますgoto (20,30,40) i。[11] C言語では同等の構文はswitch文であり、新しいFortranではSELECT CASE構文が推奨される構文の代替手段です。[12] BASICに'On GoTo'は同じ目的を達成する文がありましたが、 Visual Basicではこの構文はサポートされなくなりました。[13]
Fortran 95より前のバージョンでは、Fortranには代入gotoの亜種もあり、これは制御を文ラベル(行番号)に移し、その文ラベルは整数変数に格納(代入)されます。残念ながら、代入されていない整数変数にジャンプすることが可能であり、代入goto文に関連するバグの主な原因でした。[14] Fortranの文では、整数変数に代入できるのは定数(既存)の行番号のみです。しかし、一部のコンパイラでは、その後この変数を誤って整数として扱い、例えばインクリメントしてしまうことがあり、goto時に未指定の動作を引き起こします。次のコードは、 i行目が指定されていない場合assignの動作を示しています。
goto i
200をiに代入i = i + 1 goto i ! 未指定の動作200 write ( * , * ) "これは有効な行番号です"
いくつかの C コンパイラは、もともとgccによって導入された goto 文に関する 2 つの非標準 C/C++ 拡張を実装しています。[15]void* GNU 拡張では、単項のプレフィックスラベル値演算子 を使って、現在の関数内のラベルのアドレスを として取得できます&&。goto 命令も拡張され、任意の式にジャンプできるようになりました。この C 拡張は、それをサポートする C コンパイラのドキュメントでは計算型 gotovoid*と呼ばれています。計算型 goto の意味は Fortran の割り当て型 goto のスーパーセットです。これは、任意のポインタ式を goto 先として使用できるのに対し、Fortran の割り当て型 goto では任意の式をジャンプ先として使用できないためです。[16] C の標準 goto と同様に、GNU C 拡張では計算型 goto のターゲットを現在の関数内にのみ配置できます。現在の関数外へのジャンプを試みると、未指定の動作が発生します。[16]
BASICのいくつかの派生版では、GNU Cで用いられる意味での計算型gotoもサポートされています。つまり、ターゲットはリスト内の行番号だけでなく、任意の行番号にすることができます。例えば、MTS BASICでは、変数iGOTO i*1000の値(例えば選択されたメニューオプションを表す)の1000倍の行番号にジャンプする命令を書くことができます。[17]
PL/I ラベル変数は、計算または割り当てられた goto の効果を実現します。
1985年まで、ANSI規格COBOLALTERには、 の宛先を変更するための文がありましたがGO TO、これは段落内に単独で記述する必要がありました。このALTER文は1985年COBOL規格では廃止とみなされ、2002年に削除されました(COBOL自己書き換えコードを参照)。ポリモーフィズムを可能にするこの機能は、しばしば非難され、ほとんど使用されませんでした。[18]
Perlにはgoto文があり、関数名を受け取り、ある関数呼び出しを別の関数呼び出しに置き換えることで制御を移します(末尾呼び出し)。新しい関数はgoto文に戻るのではなく、元の関数が呼び出された場所に戻ります。[19] [説明が必要]
gotoを直接サポートしていない言語もいくつかありますが、gotoエミュレーションは制限付きではあるものの、gotoに似た機能を提供します。Java、[20]、JavaScript、[21]、Python [7] [8]ではgotoをエミュレートできます。
PL /Iのデータ型は、label代入型gotoと計算型gotoの両方を実装するために使用でき、PL/Iでは現在のブロック外への分岐が許可されています。手続きはラベルを引数として受け取り、分岐で終了することができます。ラベル変数の値にはスタックフレームのアドレスが含まれており、ブロック外へのgotoはスタックをポップします。
以下は割り当てられた goto を実装します。
whereラベルを宣言します; where = somewhere ; goto where ; ... somewhere: ...
以下は計算された goto を実装します。
where ( 5 )ラベルを宣言します。inxを固定として宣言します。where ( 1 ) = abcです。where ( 2 ) = xyzです。... where ( inx )へ移動します。... abc: ... xyz: ...
同等の結果を得る別の方法は、変数を使用しないラベル定数配列labelを使用することです。
inx を固定として宣言します。... inxにgotoします。... where ( 1 ) : ... where ( 2 ) : ...
構文は言語によって異なりますが、多くの場合、同様のパターンに従います。制御対象はラベルまたは行番号で識別されます。
DOSバッチファイルでは、gotoは実行をラベル(コロンで始まる識別子)に指示します。gotoのターゲットは変数にすることができます。以下は、gotoを使用して計算型gotoによるマルチパス分岐を実装しています。
@エコーオフSET
D8str = %date% SET D8dow = % D8str :~0,3%
FOR %% D in ( Mon Wed Fri ) do if " %% D" == " %D8dow% " goto SHOP%%D
echo今日、%D8dow%はショッピングの日ではありません。
goto end
: SHOPMon
echoランチにピザを買う - 月曜日はピザの日。
終了へ
: SHOPWed
echoカルツォーネを買って持ち帰ります - 今日は水曜日です。
終了へ
: SHOPFri
echo誰かがゼロカロリーの飲み物を欲しがっている場合に備えて、セルツァーを買ってください。
:終わり
1959年に開催されたALGOL以前の会議で、ハインツ・ゼマネクはgoto文の必要性を明確に疑問視したが、当時は誰も[引用が必要]、後にgotoの象徴的な反対者となるエドガー・W・ダイクストラを含めて彼の発言に注意を払わなかった。 [22] 1970年代と1980年代には、構造化プログラミング パラダイムが好まれ、goto文の使用は衰退し、gotoは保守不可能なスパゲッティコードにつながると批判された。GNU Pascalコーディング標準などの一部のプログラミングスタイルのコーディング標準では、goto文の使用は推奨されていない。[23]ボーム–ヤコピニの証明(1966年)では、ソフトウェア開発に構造化プログラミングを採用するかどうかの問題が解決されなかったが、その理由の1つは、その適用には追加のローカル変数の導入が必要になるため、構造化プログラミングの構築によってプログラムが改善されるよりもわかりにくくなる可能性が高いためである。[24]しかし、これはコンピュータ科学者、教育者、言語設計者、アプリケーションプログラマーの間で活発な議論を引き起こし、かつては広く使われていたgotoの使用から、ゆっくりと、しかし着実に移行していきました。gotoに対する最も有名な批判は、おそらくエドガー・ダイクストラが1968年に書いた「go-to文は有害であると考えられる」という書簡でしょう。[22]この書簡の中で、ダイクストラは、制限のないgoto文はプログラム(特にループを含むもの)の正しさの解析と検証を複雑にするため、高水準言語から廃止されるべきだと主張しました。[25]この書簡自体が議論を引き起こし、 1987年3月にCommunications of the ACM (CACM)に送られた「『GOTOは有害であると考えられる』は有害であると考えられる」という書簡[26]や、ダイクストラの「やや残念な書簡について」[27]を含む他の人々からの返信も続きました。
別の観点としては、ドナルド・クヌースの『Structured Programming with go to Statements』が挙げられます。この本では、多くの一般的なプログラミングタスクを分析し、そのうちのいくつかではgotoが最適な言語構造であると結論付けています。[28] 『The C Programming Language』の中で、ブライアン・カーニハンとデニス・リッチーはgotoが「際限なく悪用される可能性がある」と警告していますが、関数終了時のエラーハンドラやループからの複数レベルのブレークに使用できる可能性も示唆しています。[29]これらの2つのパターンは、その後のC言語に関する他の著者による多数の書籍にも見られます。[30] [31] [32] [33] 2007年の入門書では、エラー処理パターンは「C言語に組み込まれた例外処理の欠如」を回避する方法であると述べられています。[30] Linuxカーネルの設計者でプログラマーのリーナス・トーバルズや、ソフトウェアエンジニアで本の著者でもあるスティーブ・マッコーネルなどの他のプログラマーもダイクストラの見解に反対しており、gotoはプログラムの速度、サイズ、コードの明瞭性を向上させる便利な言語機能になり得るが、それは同様に賢明なプログラマーが賢明な方法で使用する場合に限られると主張している。[34] [35]コンピュータサイエンスの教授ジョン・レーガーによると、2013年にはLinuxカーネルコードに約10万回のgotoのインスタンスがあった。[36]
他の学者たちはより極端な見解を取り、ループの途中からやのような命令を実行することさえも、ベーム=ヤコピニの結果には必要ないので悪い習慣だと主張し、ループは単一の出口点を持つべきだと主張した。break[ 37]例えば、ベルトラン・マイヤーは2009年の教科書で、やのような命令は「羊の皮をかぶった古いgotoに過ぎない」と書いている。[ 38]しかし、ベーム=ヤコピニの結果をわずかに修正した形では、ループからの複数レベルのbreakが許される限り、構造化プログラミングで追加の変数を避けることができる。[39] C言語など、キーワードによる複数レベルのbreakが許可されていない言語もあるため、そのような状況ではgotoを使うようにプログラマーに勧める教科書もある。[33] MISRA C 2004標準では、goto、、および複数のand文が禁止されている。[40] 2012年版のMISRA C標準では、gotoの禁止が「必須」から「推奨」に格下げされました。2012年版には、gotoによる後方ジャンプは禁止するが前方ジャンプは禁止しないという追加の必須ルールがあります。[41] [42]returnbreakcontinuebreakcontinuereturnbreak
FORTRANは1978年に構造化プログラミング構造を導入し、その後の改訂で、goto文の使用に関する比較的緩い意味規則が厳格化されました。プログラマがgoto文を使って実行中のDOループから抜け出し、再びループに入ることができる「拡張範囲」は1978年に言語から削除され、[43]、1995年までに計算型goto文や代入型goto文を含むいくつかの形式のFortran goto文が削除されました。[44] JavaやPython など、広く使用されている現代のプログラミング言語の中にはgoto文を持たないものもありますが、ほとんどの言語では選択範囲から抜け出す、あるいは反復処理の次のステップから抜け出す、あるいは次のステップに進むための何らかの手段が提供されています。コードの制御フローを乱すことは望ましくないという考え方は、一部のプログラミング言語の設計に見られるように、例えばAda [45]では山括弧を用いてラベル定義を視覚的に強調しています。
comp.lang.c FAQリスト[46]のエントリ17.10では、goto useの問題について次のように述べられています。
プログラミングスタイルは、ライティングスタイルと同様に、ある種の芸術であり、硬直したルールで体系化することはできません。しかし、スタイルに関する議論は往々にしてそうしたルールにのみ集中しているように見えます。goto文の場合、goto文を無制限に使用すると、メンテナンスが困難なスパゲッティコードにすぐに繋がってしまうことが長年指摘されてきました。しかし、goto文を単純に、無意識に禁止したからといって、必ずしもすぐに美しいプログラミングにつながるわけではありません。構造化されていないプログラマーは、goto文を一切使わずにビザンチン構造を構築してしまう可能性があります(代わりに、奇妙にネストされたループやブール制御変数を使用するかもしれません)。多くのプログラマーは穏健なスタンスをとっています。goto文は通常は避けるべきですが、必要に応じて、制約が厳しいいくつかの状況では許容されます。例えば、複数レベルのbreak文を使用する場合、switch文内で共通のアクションをまとめる場合、複数のエラーを返す関数でクリーンアップタスクを集中させる場合などです。 (…)特定の構造を盲目的に避けたり、ルールを理解せずに従ったりすることは、ルールが本来避けるべき問題と同じくらい多くの問題を引き起こす可能性があります。さらに、プログラミングスタイルに関する多くの意見は、まさに意見に過ぎません。それらは力強く主張され、強い思い入れがあり、一見確固とした証拠や議論によって裏付けられているかもしれませんが、反対意見も同様に強く感じられ、支持され、議論されている可能性があります。「スタイル戦争」に巻き込まれるのは大抵無駄です。なぜなら、特定の問題に関しては、反対意見が決して同意できなかったり、意見の相違を認めたり、議論をやめたりできないように見えるからです。
goto の使用は全体的に減少していますが、goto がプログラムロジックを表現するのに適した方法となる状況もあります。goto を使わずにロジックを表現することは可能ですが、同等のコードはより長く、理解しにくくなります。goto が許容される可能性が高い状況としては、以下のものがあります。[34] [47]
これらの用法はCでは比較的一般的ですが、C++やより高水準の機能を備えた他の言語ではあまり一般的ではありません。[50]しかし、関数内で例外をスローしたりキャッチしたりすることは、一部の言語では非常に非効率になることがあります。その代表例がObjective-Cで、gotoの方がはるかに高速な代替手段となります。[55]
gotoのもう一つの用途は、適切にファクタリングされていないレガシーコードを修正することです。gotoの使用を避けるには、大規模なリファクタリングやコードの重複が必要になります。例えば、特定のコードのみに関心のある大規模な関数がある場合、gotoを使用すると、関数の他の部分を変更することなく、関連するコードのみにジャンプしたり、関連するコードからジャンプしたりできます。この用法はコードスメル[ 56]と見なされますが、時折使用されます。
構造化プログラミング運動は、次のような制御構造を言語に 導入することで、goto ステートメントの必要性と使用を排除することを目指しました 。
これらの新しい言語メカニズムは、以前はgoto文を使用して記述されていた同等の制御フローを置き換えました。switch文は、ジャンプ先の命令が動的(条件付き)に決定される計算型goto文に代わるものです。
特定の条件下では、レガシープログラムのローカルgoto文を複数レベルのループexit文に置き換えることで削除することが可能です。[57]
実際には、構造化プログラミングの基本的な 3 つの構造テンプレートを厳密に順守すると、構造化単位を途中で終了することができないため、高度にネストされたコードが生成され、すべての可能性のある条件を処理するために非常に複雑なプログラム状態データの組み合わせが爆発的に増加します。
一般的に採用されている解決策は 2 つあります。構造化単位を途中で終了する方法と、より一般的には例外処理です。これらは両方とも構造を上っていき、囲んでいるブロックまたは関数に制御を返しますが、任意のコード位置にジャンプすることはありません。これらは、非終端位置での return ステートメントの使用に似ています。早期終了のため厳密には構造化されていませんが、構造化プログラミングの制約がわずかに緩和されています。C では、breakおよびcontinueを使用すると、余分な while または if ステートメントを必要とせずに、ループを終了したり、次の反復に進んだりすることができます。一部の言語では、複数レベルのブレークも可能です。例外的な状況を処理するために、Java の
--などの特殊な例外処理構造が追加されました。trycatchfinally
throw-catch例外処理メカニズムも、gotoが悪用されるのと同じように、簡単に悪用されて非透過的な制御構造を作成する可能性があります。[58]
1977年にシアトルで開催されたACMカンファレンスで発表された論文の中で、ガイ・L・スティールはgotoと構造化プログラミングに関する議論を要約し、手続きの末尾位置における手続き呼び出しは、呼び出された手続きへの制御の直接的な移行として最も最適に扱うことができ、通常は不要なスタック操作操作を排除できると指摘した。[59]このような「末尾呼び出し」は、手続き呼び出しが至る所で見られる言語であるLispでは非常に一般的であるため、この形式の最適化は、他の言語で使用されるgotoと比較して、手続き呼び出しのコストを大幅に削減する。スティールは、手続き呼び出しの実装が不十分なために、gotoは手続き呼び出しに比べて安価であるという人為的な認識が生まれていると主張した。さらにスティールは、「一般的に、手続き呼び出しは、パラメータも渡すgoto文と考えるのが適切であり、機械語のJUMP命令として統一的にコード化できる」とし、機械語のスタック操作命令は「最適化とみなされる(逆ではなく!)」と主張した。[59]スティールは、Lispで最適化された数値アルゴリズムは、当時入手可能な商用Fortranコンパイラで生成されたコードよりも高速に実行できるという証拠を挙げた。これは、Lispの手続き呼び出しのコストがはるかに低いためである。スティールがジェラルド・ジェイ・サスマンと共同で開発したLisp方言であるSchemeでは、末尾呼び出しの最適化が必須となっている。[60]
Steele の論文は、少なくとも MIT で実践されていたものにおいては、コンピュータ サイエンスに目新しいことをあまり導入していなかったが、手続き呼び出し最適化の可能性を明らかにした。これにより、手続きのモジュール化を促進する特性が、複雑な内部制御構造と広範な状態データを持つ大規模モノリシック手続きという当時の一般的なコーディング習慣に代わる、より信頼性の高いものとなった。特に、Steele が論じた末尾呼び出し最適化により、手続きは単一の末尾再帰(同じ関数を呼び出す末尾再帰) による反復処理を実装する信頼性の高い方法になった。さらに、末尾呼び出し最適化では、末尾呼び出しを前提として、無制限の深さの相互再帰が可能になる。これにより、通常は goto 文で実現される有限状態マシンなどの制御の移行が可能になる。
コルーチンは、構造化プログラミングを大幅に緩和したものであり、複数の終了ポイント(末尾以外の位置での return など)だけでなく、goto 文と同様に複数のエントリ ポイントも許可します。コルーチンは goto よりも制限が厳しく、コード内の任意の位置にジャンプするのではなく、現在実行中のコルーチンを指定された位置(yield の後に継続)でのみ再開できます。コルーチンの制限された形式はジェネレータです。さらに制限されているのは、クロージャです。クロージャは、状態(静的変数経由)は維持しますが、実行位置は維持しない関数です。状態変数と構造化制御の組み合わせ、特に全体的な switch 文を使用すると、関数は後続の呼び出しで任意の位置から実行を再開できます。これは、コルーチンがない場合に goto に代わる構造化された代替手段です。これは、たとえば C でよく使用されるイディオムです。
継続は、プログラム内の任意のポイントからマークされたポイントに制御を移すという点で goto に似ています。継続は、ほとんどの構造化プログラミング言語の goto ではできない、現在の関数の外に制御を移すことができるという点で goto よりも柔軟です。ローカル変数と関数の引数を格納するためのスタック フレームを維持する言語実装では、継続を実行すると、ジャンプに加えて、プログラムの呼び出しスタックの調整が必要になります。Cプログラミング言語のlongjmp関数は、現在のコンテキストから周囲のコンテキストに脱出するために使用できる脱出継続の例です。Common Lisp のGO 演算子も、構文がレキシカル スコープであるにもかかわらず、このスタック巻き戻しプロパティを持ちます。これは、ジャンプ先のラベルをクロージャから参照できるためです。
Schemeでは、継続によって制御を外側のコンテキストから内側のコンテキストに移動することができます。これにより、コルーチンや協調型マルチタスクなどの制御構造を記述できるようになります。[60]
(著者のイニシャルにちなんで
WWG
と呼ばれることもある)は、コンピュータプログラミングに関する最初の書籍であった。