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