ソフトウェアにおいて、制御フロー(または制御の流れ)とは、あるコマンドから次のコマンドへと実行がどのように進むかを表すものです。マシンコードや命令型プログラミング言語など、多くのコンテキストでは、コマンドが別のポイントに制御を移す場合を除き、制御は順次(現在実行中のコマンドの直後のコマンドへ)進みます。この場合、そのコマンドは制御フローコマンドとして分類されます。コンテキストによっては、 「コマンド」の代わりに別の用語が使用されることもあります。例えば、マシンコードでは「命令」が、命令型言語では「ステートメント」が一般的に用いられます。
命令型言語は制御フローを明示的に記述するが、他のプログラミングパラダイムの言語は制御フローにそれほど重点を置いていない。宣言型言語は、操作の順序を規定することなく、望ましい結果を指定する。関数型言語は、通常は制御フロー文とは呼ばれないものの、言語構造と関数の両方を用いてフローを制御する。
中央処理装置(CPU)の命令セットにおいて、制御フロー命令は多くの場合、プログラムカウンタを変更し、無条件分岐(ジャンプとも呼ばれる)または条件分岐のいずれかである。別の方法として、分岐ではなく条件付きで命令を有効にする述語化がある。
割り込みやシグナルなどの非同期制御フロー転送は、通常の制御フローをハンドラに変更し、その後、制御を中断された場所に戻します。
ソフトウェアを攻撃する一つの方法は、実行フローをリダイレクトすることです。スタックカナリア、バッファオーバーフロー保護、シャドウスタック、vtableポインタ検証など、さまざまな制御フロー整合性技術が、これらの攻撃から防御するために使用されています。[ 1 ] [ 2 ] [ 3 ]
制御フローはコード構造と密接に関係しています。制御フローは、構造と言語の実行規則によって定義された経路に沿って流れます。この一般的な構造の概念は、ブロック構成に基づく順序付け、選択、反復に構造を限定する構造化プログラミングと混同してはなりません。
逐次実行は最も基本的な構造です。すべてのコードが本質的に逐次的であるとは限りませんが、命令型コードは逐次的です。
ラベルはソースコード内の位置を識別するために使用されます。一部の制御フロー文はラベルを参照し、ラベルが付けられた行に制御をジャンプさせます。位置を示す以外に、ラベルには他の効果はありません。
一部の言語では、ラベルを数値に限定しており、これは行番号と呼ばれることもありますが、これはラベルではなく、行の固有のインデックスを意味します。とはいえ、このような数値ラベルは、連続していなくても、通常はファイルの上から下に向かって増加するようにする必要があります。たとえば、BASICでは次のようになります。
10 LET X = 3 20 PRINT X 30 GOTO 10多くのプログラミング言語において、ラベルは英数字の識別子であり、通常は行頭に現れ、直後にコロンが続きます。例えば、次のC言語のコードでは、Success3行目にラベルが定義されており、そのラベルは、それに続く最初のステートメント(4行目)をジャンプ先として識別します。
void f ( bool ok ) {if ( ok ) {成功へ移動;}戻る;成功:printf ( "OK" );}ほとんどのプログラミング言語では、コードのシーケンスをブロックとして整理する機能が備わっています。制御文と併用する場合、ブロックの開始位置はジャンプ先となります。例えば、以下のC言語コード(中括弧でブロックを区切っています)では、doneがfalseの場合、制御は1行目から4行目にジャンプします。
if ( done ) {printf ( "すべて完了" );}それ以外{printf ( "まだ作業中です" );}プログラミング言語には、数多くの制御コマンドが考案されてきました。このセクションでは、注目すべき構成要素について、機能別に整理して説明します。
関数は制御フローを提供します。関数が呼び出されると、実行は関数のコードの先頭にジャンプし、関数が完了すると、制御は呼び出し元に戻ります。次のCコードでは、関数を呼び出すために、制御は6行目から2行目にジャンプしますfoo()。その後、関数本体の実行(「Hi」の出力)が完了すると、制御は呼び出し後の7行目に戻ります。
void foo () {printf ( "こんにちは" );}void bar () {foo ();printf ( "完了" );}分岐コマンドは、コード内でコマンドを含む箇所から、コマンドが指定する箇所へ実行位置を移動させます。
ジャンプコマンドは、コード内の別の箇所に無条件に制御を分岐させるものであり、コードの流れを制御する最も基本的な形式です。
高級言語では、これはしばしばgoto文として提供されます。キーワードは言語によって大文字または小文字、あるいは 1 語または 2 語の場合がありますが、次のようになります。 。制御が goto 文に到達すると、制御は指定されたラベルの次の文にジャンプします。 goto 文は、ダイクストラを含む多くのコンピュータ科学者によって有害であると考えられてきました。goto label
条件文は、ブール式の値に基づいて制御をジャンプさせます。一般的なバリエーションには以下のようなものがあります。
then、これはif文と呼ばれます。then、これはif-else文と呼ばれます。ifとを補完します。whenunlessifTrue条件を実装します。ifFalse以下のPascalコードは、単純なif-then-else文を示しています。Adaでも構文は同様です。
a > 0 の場合はwriteln ( " yes " ) 、そうでない場合はwriteln ( " no " ) ;C言語では:
if ( a > 0 ) { puts ( "yes" ); } else { puts ( "no" ); }bashでは:
if [ $a -gt 0 ] ; then echo "yes" else echo "no" fiPythonでは:
a > 0 の場合: print ( "yes" )それ以外の場合: print ( "no" )Lispでは:
( princ ( if ( plusp a ) "yes" "no" ))マルチウェイ分岐は、一致する値に基づいて制御をジャンプします。通常、一致する値が見つからない場合のデフォルトアクションが規定されています。switch文は、ルックアップテーブルなどのコンパイラ最適化を可能にします。動的言語では、ケースは定数式に限定されず、パターンマッチングに拡張される場合があります。右側のシェルスクリプトの例では、*)デフォルトケースを任意の文字列に一致するグロブとして実装しています。ケースロジックは、 SQLのステートメントのように関数形式で実装することもできますdecode。
以下のPascalコードは、比較的単純な switch 文を示しています。Pascal ではcase、 の代わりにキーワードを使用しますswitch。
case someChar of 'a' : actionOnA ; 'x' : actionOnX ; 'y' , 'z' : actionOnYandZ ; else actionOnNoMatch ; end ;Adaでは:
case someChar is when ' a ' => actionOnA ; when ' x ' => actionOnX ; when ' y ' | ' z ' => actionOnYandZ ; when others => actionOnNoMatch ; end ;C言語では:
switch ( someChar ) { case 'a' : actionOnA ; break ; case 'x' : actionOnX ; break ; case 'y' : case 'z' : actionOnYandZ ; break ; default : actionOnNoMatch ; }Bashでは:
case $someChar in a ) actionOnA ;; x ) actionOnX ;; [ yz ]) actionOnYandZ ;; * ) actionOnNoMatch ;; esacLispでは:
( case some-char (( #\a ) action-on-a ) (( #\x ) action-on-x ) (( #\y #\z ) action-on-y-and-z ) ( else action-on-no-match ))Fortranでは:
select case ( someChar ) case ( 'a' ) actionOnA case ( 'x' ) actionOnX case ( 'y' , 'z' ) actionOnYandZ case default actionOnNoMatch end select
ループは、実行時の状態に基づいて複数回実行される一連のステートメント、ループ本体です。本体は、コレクションの各項目に対して 1 回実行される (確定反復)、条件が満たされるまで実行される (不確定反復)、または無限に実行されます。ループ本体内のループは、ネストされたループと呼ばれます。[ 4 ] [ 5 ] [ 6 ] break ステートメントを使用すると、ループから早期に抜け出すことができます。[ 7 ] [ 8 ]
HaskellやSchemeのような関数型プログラミング言語では、再帰的処理と反復的処理の両方が、構文的なループ構造の代わりに末尾再帰手続きによって表現されます。
比較的単純ながら便利なループは、数値の範囲を反復処理します。単純な形式では、整数値から始まり、より大きな整数値で終わり、その間の各整数値に対して反復処理を行います。多くの場合、増分値は任意の整数値(負の値でも、より大きな値からより小さな値へとループするため)にすることができます。
BASIC言語での例:
FOR I = 1 TO N xxx NEXT IPascalの例:
for I := 1 to N do begin xxx end ;Fortranの例:
DO I = 1 、N xxx END DO多くのプログラミング言語では、整数しか使用できないか、あるいは確実に使用できません。浮動小数点数はハードウェアの制約により不正確に表現されるため、丸め誤差、ハードウェア、コンパイラなどのさまざまな要因によって、次のループは9回または10回繰り返される可能性があります。さらに、Xの増分が繰り返し加算によって行われる場合、累積した丸め誤差により、各反復におけるXの値が、一般的に期待される0.1、0.2、0.3、...、1.0という数列から大きくずれる可能性があります。
X := 0.1から1.0までステップする
ループ構造の中には、条件が真になるまで反復するものがあります。ループの開始時に条件をテストするものもあれば、終了時にテストするものもあります。開始時にテストする場合、ループ本体は完全にスキップされる可能性があります。終了時にテストする場合、ループ本体は必ず少なくとも1回は実行されます。
Visual Basic の例:
DO WHILE (テスト) xxxループPascalの例:
テストが行われるまでxxxを繰り返す。C言語系における事前テストの例:
while (テスト) { xxx }C言語系における事後テストの例:
テストが完了するまでxxxを実行します。キーワードを使用しているもののfor、この3部構成のCスタイルのループは、数値ベースではなく条件ベースの構造です。2番目の部分である条件は各ループの前に評価されるため、ループは事前テストされます。最初の部分は状態を初期化する場所であり、3番目の部分は次の反復のためにインクリメントする場所ですが、どちらの側面も他の場所で実行できます。次のCコードは、iが0からn-1まで反復する数値ループのロジックを実装しています。
for ( int i = 0 ; i < n ; ++ i ) { xxx }ループ構造の中には、コレクションの項目を列挙し、各項目に対して反復処理を行うものがあります。
Smalltalkの例:
someCollection do: [ : eachElement | xxx ] 。Pascalの例:
for Item in Collection do begin xxx end ;Rakuの例:
foreach ( item ; myCollection ) { xxx } TCLでの例:
foreach someArray { xxx }PHPの例:
foreach ( $someArray as $k => $v ) { xxx }Javaの例:
Collection < String > coll ; for ( String s : coll ) {}C#での例:
foreach ( string s in myStringCollection ) { xxx }PowerShell で「foreach」が「ForEach-Object」のエイリアスである場合の例:
someCollection | foreach { $_ }Fortranの例:
forall ( index = first : last : step ... )Scalaには、コレクション制御ループを一般化したfor式があり、非同期プログラミングなどの他の用途もサポートしています。Haskellにはdo式と内包表記があり、これらを組み合わせることでScalaのfor式と同様の機能を実現できます。
コンピュータプログラミングにおいて、無限ループ(またはエンドレスループ)[ 9 ] [ 10 ]とは、スイッチで電源を切ったりプラグを抜いたりするなど、外部からの介入がない限り、記述されたまま無限に続く一連の命令のことです。これは意図的な場合もあります。
コンピュータプログラムに無限ループが含まれているかどうかを判断する一般的なアルゴリズムは存在しない。これは停止問題と呼ばれる。
一般的なループ構造では、ステートメントや条件が繰り返されるなど、コードの重複が発生することがあります。これはさまざまな理由で発生し、コードの重複を排除または最小限に抑えるためのさまざまな解決策が提案されています。[ 11 ]従来の非構造的な解決策であるgotoステートメント以外にも、[ 12 ]一般的な構造的な解決策としては、ループ内に条件文 ( ifステートメント) を配置する (条件は重複する可能性があるが、ステートメントは重複しない) か、繰り返されるロジックを関数でラップする (関数呼び出しは重複するが、ステートメントは重複しない) などがあります。[ 11 ]
よくあるケースとしては、ループの開始部分は常に実行されるが、最後の反復では終了部分がスキップされる場合がある。[ 12 ]これはダイクストラによって「 n.5回実行されるループ」と呼ばれ、 [ 13 ]現在ではループ1.5倍問題と呼ばれている。[ 8 ]よくあるケースとしては、最初の部分でデータを読み込み、データの終了を確認し、2番目の部分でデータを処理する、または処理し、終了を確認し、次の反復の準備をするというものがある。[ 12 ] [ 8 ]これらの場合、ループの最初の部分が実行される。回実行されますが、2 番目の部分はのみ実行されます回。
この問題は少なくとも1967年以来クヌースによって認識されており、ワースは早期ループ終了によって解決することを提案した。[ 14 ] 1990年代以降、これはbreak文を使用して最も一般的に教えられている解決策であり、次のようになる。[ 8 ]
ループ文if条件break文repeat
この解決策の微妙な点は、条件が通常の while 条件とは逆であることです。while条件...繰り返しを途中で終了して書き直すには、条件を反転させる必要があります。loop ... if not condition exit ... repeat。中間にテストがあるループ制御構造は、条件を反転させることなく、ループ 1/2 の使用例を明示的にサポートします。[ 14 ]
ループ構造は、ループ文の後に別の反復処理を実行するか、実行を継続するかを決定する構造化された完了条件を提供します。しかし、多くのプログラミング言語は、さまざまな非構造化制御フロー構造をサポートしています。
continue(最も一般的)、、skip[ 15 ] (cycle Fortran)、またはnext(PerlとRuby)などです。redo同じ反復処理の本体の先頭にジャンプするステートメントがあります。retryループ全体を最初の反復から再開するステートメントがあります。[ 18 ]早期終了はループ本体の後に制御を移します[ 19 ] [ 8 ]。breakたとえば、リストを検索する場合、項目が見つかったときにループを停止できます。一部のプログラミング言語では、(ほとんどの言語)、Exit(Visual Basic)、last(Perl)などのステートメントが提供されています。
以下のAdaコードでは、Xが0のときにループが終了します。
loop Get ( X ); if X = 0 then exit ; end if ; DoSomething ( X ); end loop ;より慣用的なスタイルでは、次のように表現しますexit when。
loop Get ( X ); exit when X = 0 ; DoSomething ( X ); end loop ;Python ではbreak、ループに else 句を使用することで、ループが早期に終了したかどうか(文によって終了したかどうか)に応じて、コードを条件付きで実行できます。以下の Python コードでは、elseelse句は文にリンクされておりfor、内部のif文にはリンクされていません。Python のループforとwhileループの両方で、このような else 句がサポートされており、ループの早期終了が発生しなかった場合にのみ実行されます。
for n in set_of_numbers : if isprime ( n ): print ( "セットには素数が含まれています" ) break else : print ( "セットには素数は含まれていません" )一部の言語では、ネストされたループからの抜け出しがサポートされています。理論の世界では、これらはマルチレベルブレークと呼ばれます。一般的な使用例の 1 つは、多次元テーブルの検索です。これは、bash [ 20 ]や PHP [ 21 ]のようにマルチレベルブレーク ( Nレベルの抜け出し) を使用するか、Ada、Go、Java、Rust、Perl [ 22 ]のようにラベル付きブレーク (抜け出し、指定されたラベルで続行) を使用するかのいずれかで実行できます。マルチレベルブレークの代替手段としては、別のレベルから抜け出すためにテストされる状態変数とともに単一ブレークを使用する方法、抜け出すレベルで捕捉される例外を使用する方法、ネストされたループを関数に配置し、return を使用してネストされたループ全体を終了させる方法、またはラベルと goto ステートメントを使用する方法などがあります。C と C++ は現在、マルチレベルブレークや名前付きループをサポートしておらず、通常の代替手段は goto を使用してラベル付きブレークを実装することです。[ 23 ]しかし、この機能の導入が提案され、[ 24 ] C2Y に追加されました。[ 25 ] Java の構文に倣って。Python にはマルチレベルの break や continue はありません。これはPEP 3136で提案されましたが、追加される複雑さが正当な使用頻度の低さに見合わないという理由で却下されました。[ 26 ]
マルチレベルブレークの概念は、理論計算機科学において興味深いものであり、今日ではコサラジュ階層と呼ばれるものを生み出す。[ 27 ] 1973年、S. ラオ・コサラジュは、ループからの任意の深さのマルチレベルブレークが許容される限り、構造化プログラミングで追加の変数を追加せずに済むことを証明することで、構造化プログラム定理を改良した。 [ 28 ]さらに、コサラジュは、厳密なプログラムの階層が存在することを証明した。すなわち、任意の整数nに対して、追加の変数を導入せずにn未満の深さのマルチレベルブレークを持つプログラムに書き換えることができない、深さnのマルチレベルブレークを含むプログラムが存在する。[ 27 ]
デイビッド・ワットは、2004年の教科書で、テネントのシーケンサーの概念を用いて、マルチレベルブレークとリターン文の類似性を説明しています。ワットは、エスケープシーケンサーと呼ばれるシーケンサーのクラス(「テキストで囲まれたコマンドまたはプロシージャの実行を終了するシーケンサー」と定義される)には、ループからのブレーク(マルチレベルブレークを含む)とリターン文の両方が含まれると指摘しています。しかし、一般的に実装されているリターンシーケンサーは(戻り)値も保持できますが、現代の言語で実装されているブレークシーケンサーは通常保持できません。[ 29 ]
1972年にダールによって以下の構造が提案されました。 [ 30 ]
ループループ xxx1 read(char); while test; while not atEndOfFile; xxx2 write(char); 繰り返し; 繰り返し;
ここでの構造は、中間にwhileチェックがあるdoループと考えることができ、明確な1.5 ループのロジックが可能になります。さらに、個々のコンポーネントを省略することで、この単一の構造は、ほとんどのプログラミング言語の複数の構造を置き換えることができます。xxx1を省略すると、テストが上部にあるループ (従来のwhileループ) になります。xxx2 を省略すると、テストが下部にあるループになり、多くの言語のdo whileループと同等になります。while を省略すると、無限ループになります。この構造では、条件が中間にある場合でも同じ極性を維持できます。これは、極性を反転 (not を追加)する必要がある早期終了とは異なり、[ 14 ] whileの代わりにuntilとして機能します。
この構造は広くサポートされておらず、ほとんどの言語では代わりに条件付き早期終了のためにif ... break を使用します。
これは、構文が BEGIN ... WHILE ... REPEAT であるForthなどの言語[ 31 ]や、構文がwhile ... do ... doneまたはuntil ... do ... doneであるシェルスクリプト言語Bourne shell ( sh) およびbashなどによってサポートされています。例: [ 32 ] [ 33 ]
while statement-1 statement-2 ... condition do statement-a statement-b ... doneシェル構文が機能するのは、while(またはuntil)ループが条件としてコマンドのリストを受け入れるためである。 [ 34 ]正式には、次のようになる。
テストコマンドが実行される間、後続コマンドを実行する。
テストコマンドのリストの値(終了ステータス)は最後のコマンドの値であり、これらは改行で区切ることができ、上記の慣用的な形式になります。
C言語やC++言語では、カンマ演算子を使って同様の構造を実現できます。また、同様の構造を持つ他の言語でも、while条件の中にステートメントのリストを無理やり詰め込むことができます。
while ( statement_1 , statement_2 , condition ) { statement_a ; statement_b ; }合法ではあるものの、これは限定的であり、もし使用されるとしても、主に短い修正してからテストするケースに限られます。例: [ 35 ]
while ( read_string ( s ), strlen ( s ) > 0 ) { // ... }ループのバリアントとループの不変量は、ループの正しさを表現するために使用されます。[ 36 ]
実際には、ループバリアントとは、初期値が非負である整数式のことです。バリアントの値はループの各反復処理中に減少する必要がありますが、ループが正しく実行される間は決して負の値になってはなりません。ループバリアントは、ループが確実に終了することを保証するために使用されます。
ループ不変条件とは、最初のループ反復の前に真でなければならず、各反復後も真であり続ける必要がある条件です。これは、ループが正しく終了する場合、終了条件とループ不変条件の両方が満たされることを意味します。ループ不変条件は、連続する反復中にループの特定の特性を監視するために使用されます。
Eiffelなどの一部のプログラミング言語は、ループのバリアントと不変条件をネイティブにサポートしています。一方、 Javaモデリング言語のJavaにおけるループ文の仕様のように、サポートがアドオンとして提供される場合もあります。
Lisp の方言の中には、ループを記述するための広範なサブ言語を提供するものがあります。初期の例としては、Interlispの Conversional Lisp が挙げられます。Common Lisp [ 37 ]は、そのようなサブ言語を実装する Loop マクロを提供しています。
while (true)for (init; test; increment)range()while関数を使用すれば可能です。std::for_eachretryが、それは例外処理、ループ制御では使用されません。GO TO。for多くのプログラミング言語、特に動的なプログラミングスタイルを好む言語は、現在の実行ポイントから事前に宣言されたポイントへ実行をジャンプさせる非ローカル制御フローのための構造を提供している。以下にその代表的な例を示す。
初期のFortranコンパイラは、例外的な条件を処理するためのステートメントをサポートしていました。これにはIF ACCUMULATOR OVERFLOW、、、、IF QUOTIENT OVERFLOWなどがIF DIVIDE CHECK含まれます。マシン非依存性を考慮して、これらは FORTRAN IV および Fortran 66 標準には含まれませんでした。しかし、Fortran 2003 以降では、モジュール内の関数を呼び出すことで数値的な問題をテストすることが可能ですIEEE_EXCEPTIONS。
PL/Iには、約22種類の標準条件(例:ZERODIVIDE SUBSCRIPTRANGE ENDFILE)があり、これらはON条件アクションによって発生およびインターセプトできます。プログラマは独自の名前付き条件を定義して使用することもできます。
非構造化if文と同様に、指定できるステートメントは1つだけなので、多くの場合、制御フローを再開する場所を決定するためにGOTO文が必要になります。
残念ながら、一部の実装では空間と時間の両面でかなりのオーバーヘッドが発生したため(特にSUBSCRIPTRANGE)、多くのプログラマーは条件式の使用を避けようとした。
構文の典型的な例:
ON条件GOTOラベル
多くの現代的なプログラミング言語は、例外処理をネイティブにサポートしています。一般的に、例外制御フローは、例外オブジェクトがスロー(発生)されることから始まります。制御は、コールスタックの最も内側の例外ハンドラへと進みます。ハンドラが例外を処理すると、フロー制御は通常の状態に戻ります。そうでない場合は、制御は外側のハンドラへと進み、いずれかのハンドラが例外を処理するか、プログラムが最も外側のスコープに到達して終了します。制御が徐々に外側のハンドラへと進むにつれて、コールスタックのポップなど、通常であれば発生するはずの処理は自動的に行われます。
以下の C++ コードは、構造化例外処理を示しています。例外が の実行から伝播しdoSomething()、例外オブジェクトの型が catch 句で指定された型のいずれかと一致する場合、その句が実行されます。たとえば、SomeExceptionによって 型の例外が伝播された場合doSomething()、制御は 2 行目から 4 行目にジャンプし、「Caught SomeException」というメッセージが表示された後、 tryステートメントの後の 8 行目に制御がジャンプします。その他の型の例外が伝播された場合は、制御は 2 行目から 6 行目にジャンプします。例外が発生しない場合は、制御は 2 行目から 8 行目にジャンプします。
試す{doSomething ();} catch ( const SomeException & e )std :: println ( "Caught SomeException: {}" , e.what ( ) ) ;} catch (...) {std :: println ( "不明なエラー" );}doNextThing ();多くの言語では C++ のキーワード ( throw、tryおよびcatch) を使用しますが、一部の言語では他のキーワードを使用します。たとえば、Ada ではexception例外ハンドラを導入するために を使用し、whenの代わりに を使用しますcatch。AppleScriptでは、次の AppleScript コードに示すように、構文にプレースホルダを組み込んで例外に関する情報を抽出します。
try myNumberをmyNumber / 0に設定on error e number n from f to t partial result pr if ( e = "ゼロで割ることはできません" ) then display dialog "そのような操作は行わないでください" end try多くの言語(Object Pascal、D、Java、C#、Pythonなど)では、 try文finallyの末尾にある句はtry、try文の残りの部分から例外が伝播するかどうかに関わらず、try文の最後に実行されますtry。次のC#コードは、ストリームstreamが閉じられることを保証します。
FileStream stream = null ; try { stream = new FileStream ( "logfile.txt" , FileMode.Create ); return ProcessStuff ( stream ) ; } finally { if ( stream ! = null ) { stream.Close ( ) ; } }このパターンは一般的であるため、C# ではusingクリーンアップを確実に行うためのステートメントが用意されています。以下のコードでは、ProcessStuff() が例外を伝播した場合でも、streamオブジェクトは解放されます。Python のwithステートメントと Ruby のブロック引数もFile.open同様の効果を発揮します。
using ( FileStream stream = new ( "logfile.txt" , FileMode.Create ) ) { return ProcessStuff ( stream ) ; }コンピュータサイエンスにおいて、継続とはコンピュータプログラムの制御状態を抽象的に表現したものです。継続はプログラムの制御状態を実装(具体化)します。つまり、継続はプロセスの実行における特定の時点での計算プロセスを表すデータ構造です。作成されたデータ構造は、実行環境内で隠蔽されるのではなく、プログラミング言語からアクセスできます。継続は、例外、ジェネレータ、コルーチンなど、プログラミング言語における他の制御メカニズムをエンコードするのに役立ちます。
「現在の継続」または「計算ステップの継続」とは、実行中のコードの観点から見て、プログラムの実行における現在の時点から派生する継続のことです。継続という用語は、第一級継続を指す場合にも使用されます。第一級継続とは、プログラミング言語が任意の時点で実行状態を保存し、プログラムの後の時点でその時点に戻る(場合によっては複数回)ことを可能にする構成要素です。
コンピュータサイエンスでは、ジェネレータはループの反復動作を制御するために使用できるルーチンです。すべてのジェネレータはイテレータでもあります。[ 40 ]ジェネレータは、パラメータを持ち、呼び出し可能で、値のシーケンスを生成するという点で、配列を返す関数と非常によく似ています。ただし、すべての値を含む配列を作成して一度にすべて返す代わりに、ジェネレータは値を一度に1つずつ生成するため、必要なメモリが少なく、呼び出し元は最初のいくつかの値をすぐに処理し始めることができます。つまり、ジェネレータは関数のように見えますが、イテレータのように動作します。
ジェネレータは、コルーチンや第一級継続などの、より表現力豊かな制御フロー構造で実装できます。[ 41 ]セミコルーチンとも呼ばれるジェネレータ[ 42 ]は、ジャンプ先のコルーチンを指定するのではなく、常に呼び出し元に制御を戻す(値を渡すとき)という点で、コルーチンの特殊なケースであり、コルーチンよりも弱いものです。コルーチンとジェネレータの比較を参照してください。
コルーチンは、中断および再開が可能なコンピュータプログラムの構成要素であり、協調的なマルチタスク処理のためにサブルーチンを一般化したものです。コルーチンは、協調タスク、例外処理、イベントループ、イテレータ、無限リスト、パイプなど、おなじみのプログラム構成要素の実装に最適です。
これらは「実行を一時停止できる関数」と表現されている。[ 43 ]
メルビン・コンウェイは1958年にアセンブリプログラムの構築に適用した際にコルーチンという用語を作り出した。[ 44 ]コルーチンの最初の公開された説明は、その後1963年に発表された。[ 45 ]
コンピュータプログラミングにおいて、COMEFROM は、 COMEFROM 引数で指定された位置に制御が到達したときに、その次のステートメントに制御をジャンプさせる制御フローステートメントです。このステートメントはgotoの反対として意図されており、真面目なコンピュータサイエンスというよりはジョークとして扱われることが多いです。多くの場合、指定されたジャンプポイントはラベルとして識別されます。たとえば、 は、COMEFROM x制御がラベル に到達したときにx、COMEFROM の次のステートメントから制御が継続されることを指定します。
goto文との大きな違いは、goto文がコードのローカル構造に依存するのに対し、COMEFROM文はグローバル構造に依存する点です。goto文は、制御が指定された文に到達した時点で制御を移しますが、COMEFROM文では、プロセッサ(つまりインタプリタ)がCOMEFROM文をスキャンし、制御が指定されたいずれかの箇所に到達した時点でジャンプを実行する必要があります。結果として生じるロジックは、ジャンプ箇所付近に実際に制御がジャンプするという兆候がないため、理解しにくい傾向があります。プログラム全体を調査して、COMEFROM文がその箇所を参照しているかどうかを確認する必要があります。
COMEFROM ステートメントの意味は、プログラミング言語によって異なります。一部の言語では、指定されたポイントのステートメントが実行される前にジャンプが発生し、他の言語ではジャンプが後に発生します。言語によっては、同じポイントを参照する複数の COMEFROM ステートメントが無効であったり、非決定論的であったり、特定の順序で実行されたり、スレッドインターカルで見られるように並列処理やその他の並行処理を引き起こしたりする場合があります。
COMEFROM は、当初はジョークのアセンブリ言語命令のリスト(「CMFRM」として) に見られました。これは、1973 年に R. Lawrence Clark がDatamation の記事[ 46 ]で詳しく説明したもので、 Edsger Dijkstraの手紙「Go To ステートメントは有害である」への返答として書かれました。COMEFROM は最終的に、難解なプログラミング言語INTERCALの C-INTERCAL バリアントに、さらに難解な「computed COMEFROM」とともに実装されました。また、 「assigned COME FROM」と (既存の「DO」ループを補完する) 「DONT」ステートメントのFortran提案もありました[ 47 ]。
ザーンの構成は1974年に提案され[ 48 ] 、クヌース(1974)で議論された。ここでは修正版を提示する。
イベントAまたはイベントBまたはイベントCが発生した場合に終了します。 xxx 出口 イベントA: アクションA イベントB: アクションB イベントC: アクションC endexit ;
exitwhenは、 xxx内で発生する可能性のあるイベントを指定するために使用されます。イベントの発生は、イベント名をステートメントとして使用することで示されます。何らかのイベントが発生すると、関連するアクションが実行され、その後、endexit の直後に制御が移ります。この構造により、ある状況が適用されるかどうかを判断することと、その状況に対して実行すべきアクションとの間に、非常に明確な分離が実現されます。
exitwhenは概念的には例外処理に似ており、多くのプログラミング言語では例外やそれに類する構造がこの目的で使用されています。
以下の簡単な例では、2次元テーブルから特定の項目を検索する例を示します。
見つかったか見つからない場合は終了します。I := 1からNまで、 J := 1からMまで、 table[I,J] がターゲットと一致する場合は見つかりました。 ない; 出口 見つかりました: print("アイテムはテーブル内にあります"); 欠落: print("項目がテーブルにありません"); endexit ;go to