Movatterモバイル変換


[0]ホーム

URL:


コンテンツにスキップ
Wikipedia
検索

制御構造

出典: フリー百科事典『ウィキペディア(Wikipedia)』
この記事は検証可能参考文献や出典が全く示されていないか、不十分です。出典を追加して記事の信頼性向上にご協力ください。このテンプレートの使い方
出典検索?"制御構造" – ニュース ·書籍 ·スカラー ·CiNii ·J-STAGE ·NDL ·dlib.jp ·ジャパンサーチ ·TWL
(2025年9月)

制御構造(せいぎょこうぞう)は、コンピュータプログラミング言語、特に手続き型プログラミング[1]命令型プログラミング[2]において、ループや飛び越しなどといった、手続き(プロシージャ)中の実行順を順次実行から変化させたり、サブルーチン呼出しやその戻り、などといった制御を行う「 」などの構造(言語の構成要素)である[3]

制御構造の種類は言語によって様々だが、典型的には以下のようなものがある(用語「ブロック」については、ブロック の記事を参照)。

  • 無条件に実行箇所を移動する(無条件の分岐命令、ジャンプ)
  • 何らかの条件の成立・不成立に従い、ブロックの実行・不実行を選択する(条件付き分岐命令、選択)
  • ブロックを繰り返し実行する(ループ
  • ジャンプの一種だが、その続きに戻れるもの(サブルーチン呼出、コルーチン
  • 継続(特にcall/cc)
  • プログラムの停止(理論的には重要だが(停止性問題を参照)、実際的にはexitシステムコールなど、OSのプロセス制御機構を使うことが専らであり、言語機能として制御構造で持つ意味は無い)

割り込みシグナルは制御フローを変化させる別の機構であり、サブルーチンに似ているが、通常は言語内からではなく外部のイベントなどの結果として非同期に発生するものである(言語内から外部のイベントを引き起こし、結果として発生させることもできる例もある。たとえば、ゼロ除算などでそのようになるシステムがある)。自己書き換えコード副作用によって制御フローを変化させることができる。割り込み的なものを扱うことができるプログラミング言語(の機能)はいくつかある。自己書き換えをプログラマが明示的に扱えるプログラミング言語はあまりないが、初期化の時だけ特別扱いが必要といったコードの最適化に自己書き換えを利用する、処理系の実装上のテクニックといったようなものもある。

→「分岐命令」も参照

機械語において制御構造に相当するのは分岐命令で、通常は連続的にカウントが進められるプログラムカウンタを、不連続に変更する命令である。ほぼ全てのプロセッサは(条件付きおよび無条件の)分岐命令を持つ(無いと、理論的に言うと計算可能性を満たさない[4]ため、コンピュータとして成立しない)。また、サブルーチン呼出しを(大なり小なりハードウェアによる支援を含んで)サポートする命令を持つプロセッサが多い(こんにち存在する汎用プロセッサの著名なISAでこのサポートの無いのは、60年代の設計であるIBMのSystem/360ぐらいであろう)。全くハードウェアによるサポートが無いと、サブルーチン呼出しに面倒なトリックが必要なことがある。一方で前述のような、ループの処理(レジスタの値をデクリメントし、ゼロでなければジャンプ、といったような)を直接サポートするような命令を持つプロセッサは、専用命令として積極的に持つものもあるが、一般にはあまり多くなく、特にいわゆるRISCでは避けられる。そのため、コンパイラのコード生成は制御構造からジャンプ命令等を適宜組み合わせたコードを生成するように実装される。

原始的な機能

[編集]

ラベル

[編集]
→詳細は「ラベル (プログラミング)」を参照

ラベルとは、コード中の固定の位置を示す何らかのシンボルであり、gotoの飛び先や、breakで抜ける対象として参照される。GCC拡張であるが、void * ポインタの値として扱うこともできる(Labels as Values[5])。

行番号は一部の言語(FORTRANBASIC)でラベルの一種として使われ、負でない整数がソースコードの各テキスト行の先頭に置かれる。行番号を使用する言語では、連続で実行される文には行番号が増えるように行番号を与える必要がある、とBASICしか知らない者は誤解しているが、FORTRANにはそのような制限は無い。BASICで行番号が昇順なのは、テキストの編集にフルスクリーンエディタが一般的ではなかった時代のラインエディタのみによる編集では、行番号に従ってシステムが並べ直してくれたほうが便利だったからであり、「行番号が増えるように行番号を与える必要がある」というのは本末逆転である。例えば BASIC では次のようになっている。

10LETX=320PRINTX

CAdaといった言語のラベルは識別子であり、文の前に書かれ、その直後にコロンが書かれる。例えば C では次のようになる。

Success:printf("The operation was successful.\n");

Algol 60 言語はラベルとして識別子も非負整数も使用可能(どちらもその後にコロンが続く)だが、多くのAlgol系言語では非負整数をラベルとして許容していない。

goto

[編集]
→詳細は「Goto文」を参照

goto 文は最も典型的な無条件のジャンプである。キーワードとしては大文字だったり小文字だったり空白が入ってgo toだったり、単にgoだったりするが、その構文はだいたいのものが以下のようになっている。

gotolabel

goto 文の実行により、その次に実行する文は、ラベルが示す箇所の直後の文となる。

サブルーチン

[編集]
→詳細は「サブルーチン」を参照

サブルーチンには、手続き、ルーチン、プロシージャ、関数(特に値を返す場合)、メソッド(特に何らかのクラスに属する場合)など様々な名称がある。

1950年代、コンピュータのメモリは非常に小さかったため、サブルーチンの第一の目的はプログラムのサイズを削減することにあった。サブルーチンとして書かれたコードをプログラム内のあちこちから使用することでプログラム全体のコードサイズを削減したのである。現在ではサブルーチンはプログラムを構造化するために使われる。すなわち、特定のアルゴリズムを分離したり、特定のデータにアクセスするメソッドを隠蔽したりする。多数のプログラマが共同でプログラム開発をする場合、サブルーチンはある種のモジュール性を提供し、仕事の分割点の役割も果たす。

サブルーチンに引数があればさらに便利になる。多くのプログラミング言語には平方根を求めるサブルーチンが組み込まれており、引数として平方根を求めたい数を与えることができる。

プログラミング言語によっては再帰呼び出しが可能である。つまり、サブルーチンが直接的あるいは間接的に自分自身を呼び出すことができる。クイックソート木構造を探索するアルゴリズムなどは再帰を使った方が素直に表現できる。

サブルーチンを使用すると、引数の受け渡し、サブルーチン呼び出し、コールスタック処理、サブルーチンからの復帰などのオーバヘッドによりプログラム性能が若干低下する。実際のオーバヘッドはハードウェアおよびソフトウェアのアーキテクチャに依存する。コンパイラによってはインライン展開を効果的に使用してオーバヘッドの低減を図るものもある。

プログラミング言語によってはサブルーチンの物理的な最後尾に到達しないとサブルーチンから復帰できない方式のものもある。他の言語にはreturnexit 文がある。これはサブルーチンの最後尾への分岐と等価であり、制御構造を複雑化するものではない。必要に応じて複数のそれらの文をサブルーチン内に置くことができる。

必要最小限の構造化制御フロー

[編集]
→「構造化プログラミング」も参照

1966年、Böhm と Jacopini は Communications of the ACM 誌で論文を発表し[6]goto を使って書かれたプログラムが選択 (IF THEN ELSE) とループ (WHILE condition DO xxx) のみを使って goto を使わずに書き換えられることを示した(コードの一部を複製したり、真理値フラグ変数を追加する必要がある)。後に彼らは選択もループ(と追加の真理値変数)で置き換え可能であることを示した(「構造化定理」)。

非常に良く誤解されているが、そのような書き換えが可能という事実は、単に「機械語で書けば何でも書ける」という事実と同程度の意味しかなく、それが望ましいということは全く意味しない。理論的(理論計算機科学的)にはコンピュータは一種類の命令、たとえば「subtract one number from another and branch if the result is negative」さえあれば何でもできるが(チューリング完全あるいは「万能」、OISC も参照)全く実用的ではなく、実際のコンピュータは多数の命令を備えているということと類似している。

Böhm と Jacopini の論文は全てのプログラムから goto 文を無くすことができることを示した。

また、他の研究により入口と出口がそれぞれひとつになっている制御構造が他の構造よりも理解し易いということが示された。特にそのような制御構造はプログラムの任意の箇所に制御構造を乱すことなく挿入可能な点が有利とされた。入口と出口がそれぞれひとつだと、資源の確保と解放などを対になるように書けば、かならず対になるように実行される、という利点があるため、コーディング規則でも場合によりそのような規則が適用される場合がある。

しかし実は、「理論に従ってgoto 文を無くしたプログラム」が「理解し易い」ものであるか否かは不明であり、実際のところどうなるかというと、en:Structured program theorem#Single-while-loop, folk version of the theorem にあるようなコードになる。「理解し易い」かどうかは読者に任せる。

構造化された制御要素

[編集]
→「構造化プログラミング」、「ブロック (プログラミング)」、「オフサイドルール」、および「予約語」も参照

以下ではなぜかキーワードに変にこだわっているが、そういった字句にこだわるのではなく、構文(シンタックス)として総合的に捉えれば、たいしてこだわる意味はない。この節冒頭に挙げたリンク先の各記事を参照。

終了キーワードがない言語
Algol 60CC++HaskellJavaPascalPerlPHPPL/IPythonPowerShellなど。この種の言語は文の並びをひとまとめ(ブロック)にする何らかの方法を持っている。
  • Algol 60、Pascal:begin ...end
  • C、C++、Java、Perl、PHP、PowerShell: 中括弧を使用{ ...}
  • PL/1:DO ...END
  • Python: インデントのレベルを使用(オフサイドルール参照)
  • Haskell: インデントのレベルか中括弧を使用でき、それらを自由に混合可能
終了キーワードがある言語
AdaAlgol 68Modula-2Fortran 77Visual Basic など。終了キーワードはいくつかの種類がある。
  • Ada、Fortran 90: 終了キーワードはend + 空白 + 開始キーワード。例えば、if ...end if,loop ...end loop
  • Algol 68: 開始キーワードを逆に綴る。例えば、if ...fi,case ...esac
  • Fortran 77: 終了キーワードはend + 開始キーワード。例えばIF ...ENDIF,DO ...ENDDO
  • Modula-2: 開始キーワードに関わらず常にEND という終了キーワードを使う。
  • Visual Basic: 制御構造毎に固有の終了キーワード。If ...End If;For ...Next;Do ...Loop;While ...Wend

選択

[編集]

if-then-(else)

[編集]
→詳細は「if文」を参照

条件式と条件付き実行は、条件節(たいていは「式 (プログラミング)」)の評価結果の真偽によって異なる式やブロックを選択実行する。

IF..GOTO
非構造化言語に見られる形式で、典型的な機械語命令をそのまま言語に持ってきたものである。条件が真なら指定されたラベル(または行番号)へジャンプ (GOTO) する。
IF..THEN..(ENDIF)
ジャンプに限らず、単純な文や入れ子になったブロックを THEN というキーワードの後に置くことができる。構造化された形式である。
IF..THEN..ELSE..(ENDIF)
上と同じだが、条件が偽の場合の動作も記述できる。これが最も一般的な形式で、様々なバリエーションがある。終了キーワードENDIF が必要な場合とそうでない場合がある。C言語やそこからの派生言語では終了キーワードは不要で、'then' に相当するキーワードも不要なことが多いが、その場合は条件式を括弧で囲む必要がある(といったような変な覚え方をするより、BNFを読んで構文規則を理解してしまったほうが早い)。

elseif

[編集]

「宙ぶらりんelse問題(dangling else problem、en:Dangling else)」も関係するのだが、文法の設計(デザイン)によっては、

IF cond THEN  ...ELSE  IF cond THEN    ...  ELSE    IF cond THEN      ...    FI  FIFI

のように、「複数の場合に対する場合分け」の単純な多分岐であるにもかかわらず、どんどんネストが深くなるような書き方をせざるをえない場合がある(C言語の文法の設計は、こうなってはいない)。これは ELSEIF のようなキーワードの導入で解決できる。elseif,elsif,elif など言語によるバリエーションが多いので、テキストエディタによるリアルタイムなシンタックスハイライトが非常に有効である。言語によってはelse if という「2語から成るキーワードのようなもの」という設計のものもある。

Pascal:C:シェルスクリプト:Python:Lisp:Smalltalk:
ifa>0thenbeginwriteln("yes")endelsebeginwriteln("no")end
if(a>0){printf("yes");}else{printf("no");}
if[$a-gt0]thenecho"yes"elseecho"no"fi
ifa>0:print"yes"else:print"no"
(princ(if(pluspa)"yes""no"))
Transcriptshow:(a>0ifTrue:  ['yes'  ]ifFalse:  ['no'  ]).

あまり一般的でないバリエーションとして、以下のような例がある。

  • FORTRANなどの一部の言語では、3方向の分岐を扱う「算術IF文」があり、数値を正か、ゼロか、負か判定して処理を分岐させる。
  • 多くの関数型言語などではif文が関数や式として実装されており、そのようなifは評価した式の結果を返す。
  • 一部の言語ではif文演算子の様に実装されており、例えばC言語の条件演算子がある。
  • PerlではC言語風のif だけでなく、whenunless や、コードの後に条件式が来るif がある。
  • Smalltalkでは言語組込みの機能としてではなく、ifTrueifFalse というメッセージに手続き引数を与えることで、条件付き実行ができる。

一般論として、関数の引数を積極評価してしまう言語では、条件実行のようなものを関数にできない。遅延評価のような機構が何かあれば、条件実行を特に言語機能にしなくても、引数を遅延評価する関数によって、条件実行もできる。

パターンマッチング

[編集]
→「パターンマッチング」を参照

ここではOCamlの例を挙げる。

match fruitwith| "apple"-> cook pie| "coconut"-> cook dango_mochi| "banana"-> mix;;

switchとcase

[編集]
→詳細は「switch文」を参照

switch文(言語によっては「case文」)は、指定された値を指定された定数群と比較し、最初に一致した定数に従ってその後の処理を決定するものである。一般にどの定数とも一致しなかった場合を想定したデフォルト動作を 'else' や 'otherwise' などとして用意しておく。ルックアップテーブルなどを使ったコンパイラ最適化が可能である。動的プログラミング言語では比較対象が定数式である必要はなく、パターンマッチに拡張することが可能である。例えば下記のシェルスクリプトの例で'*)' は任意の文字列にマッチングする正規表現を使ってデフォルト動作を指定している。SQLdecode のように、関数のような見た目のものもある。

Pascal:C:シェルスクリプト:
casesomeCharof'a':actionOnA;'x':actionOnX;'y','z':actionOnYandZ;elseactionOnNoMatch;end;
switch(someChar){case'a':actionOnA;break;case'x':actionOnX;break;case'y':case'z':actionOnYandZ;break;default:actionOnNoMatch;}
case$someCharina)actionOnA;;x)actionOnX;;[yz])actionOnYandZ;;*)actionOnNoMatch;;esac

ループ

[編集]
→詳細は「ループ (プログラミング)」を参照

ループはソースコード上で1回だけ書かれた文の並びを連続して複数回実行することである。ループの「中」のコード(本体と呼び、下記の例ではxxx で表されている)は指定回数実行されるか、指定されたコレクションの各要素に対応して実行されるか、何らかの条件が成立するまで繰り返し実行される。無限に繰り返されることもある。

SchemeHaskellのような関数型言語では、ループより再帰呼び出し不動点コンビネータを使用してプログラミングするのが普通である。末尾再帰は再帰呼び出しの特殊ケースであり、容易にループに変換できる(正確には「末尾呼び出しは容易にジャンプに変換できる。末尾再帰は末尾呼び出しの対象が自身になっているという特殊ケースであり、ループに変換できる」)。

カウント制御ループ

[編集]
→詳細は「for文」を参照

指定された回数だけブロックを繰り返すループである。本来、その回数だけを指定するなどもっと抽象化されているべきであるが、「ループ変数」などを指定するなど煩雑さがともなっているものが多い。以下の例で N が 1 より小さい場合、ループ本体は全く実行されない。カウントは多くの場合増える方向だけでなく減る方向にも設定可能で、1回に増える量も 1 以外に設定できることが多い(Pascalだけが±1にしかできない)。

   FOR I = 1 TO Nfor I := 1to Ndobegin       xxx                       xxx   NEXT Iend;   DO I = 1,Nfor ( I=1; I<=N; ++I ) {       xxx                       xxx   END DO                    }

多くのプログラミング言語では、カウント制御ループでは整数のみが使われる。浮動小数点数はハードウェアの制限により精度に限界がある(「ハードウェアの制限により」ではない。そもそも間違ったプログラミングなのである)。従って次のようなループでは、

for X := 0.1step 0.1to 1.0do

繰り返し回数が9回の場合と10回の場合がある。これは丸め誤差やハードウェアやコンパイラの違いによって変わってくる(といったように過去には信じている者が多かった。現代ではIEEE 754で標準化されているのだが、このようなプログラミングをしてはならないことに変わりはない)。さらに言えば、Xに繰り返し加算すると丸め誤差が累積していき、想定した数列である 0.1, 0.2, 0.3, ..., 1.0 からかけ離れていくことがありうる(「ありうる」ではなく、「そうなる」と表現すべき、浮動小数点表現による現象である。なお、このような例ばかりを想定して、十進浮動小数点では誤差が出ない、とナイーブに考える者が非常に多いが、間違いである)。

条件制御ループ

[編集]
→詳細は「while文」および「do-while文」を参照

条件(条件式)が指定されており、その式を評価した結果が真であれば(あるいは、偽であれば)ループを繰り返す。条件のテストがループの先頭にある場合と最後にある場合がある。前者の場合、ループ本体を全く実行しないことがありうるが、後者の場合は少なくとも1回はループ本体を実行する。

   DO WHILE (test)repeat        xxx                       xxx    END DOuntil test;while (test) {do       xxx                       xxx   }while (test);

コントロールブレイク英語版は通常のループ内で値の変化を検出する手段として使われ、値のグループの処理のトリガーとなる。ループ内で変化する値(群)をキーで監視し、可変な値に関連したグループイベント処理へとプログラムのフローを変換する。

   DO UNTIL (End-of-File)      IF new-zipcode <> current-zipcode         display_tally(current-zipcode, zipcount)                  current-zipcode = new-zipcode         zipcount = 0      ENDIF            zipcount++   LOOP

コレクション制御ループ

[編集]
→詳細は「Foreach文」および「イテレータ」を参照

一部のプログラミング言語(例えば、AdaD言語SmalltalkPHPPerlObject PascalJavaC#Visual BasicRubyPythonJavaScriptFortran 95 およびそれ以降)では、明示的に配列や集合やコレクションの全要素に対応してループを回すことができる。

   someCollectiondo: [:eachElement |xxx].for Itemin Collectiondobegin xxxend;foreach (item; myCollection) { xxx }foreach someArray { xxx }foreach (someArray as $k => $v) { xxx }   Collection<String> coll;for (String s : coll) {}foreach (string sin myStringCollection) { xxx }   $someCollection | ForEach-Object { $_ }forall ( index = first:last:step... )

汎用の繰り返し

[編集]

C言語のfor 文やCommon Lispdo のような汎用性の高い繰り返し要素を使えば、前述の各種ループもその他のループも実現できる。例えば、複数のコレクションを並列に回したりできる。もっとも、個別のループ構造がある場合、そちらを使った方がコードの目的をより明確に表現できるとも言える。

無限ループ

[編集]

場合によっては無限にループする方がプログラムに適していることもあるし、何らかのエラーが発生するまでループするという場合もある。実際、イベント駆動型プログラムサーバなど)はイベント制御ループを永遠に回り続け、プロセスが操作者によって終了させられたときだけループを停止する。

ただし一般には、無限ループはプログラミングのミスで発生する。すなわち、ループ終了条件がループ内で全く発生しないことが原因で意図しない無限ループとなる。

次の繰り返しへの継続

[編集]
→詳細は「continue文」を参照

ループ途中でループ処理を中断してループの先頭に戻り、次の繰り返しを開始したい場合がある。言語によってはこれを実現するcontinue とかskipnext といった文を用意している。その効果は最も内側のループ本体の実行を途中で止め、そのループの次の繰り返しを最初から行う。もしそのときの実行が最後の繰り返しであった場合、ループそのものを早期に終了させるのと同じことになる。

現在の繰り返しの再実行

[編集]

Perl や Ruby といった一部の言語ではredo 文によって現在の繰り返しを先頭から再実行することができる。

ループの再実行

[編集]

Ruby では、retry 文でループ全体を最初から再実行することができる。

ループからの早期脱出

[編集]
→詳細は「break文」を参照

カウント制御型ループを使って配列上のデータを検索している際に、必要な要素を見つけたら即座にループから抜け出したいという状況がありうる。プログラミング言語によってはbreak とかexitlast といった文を用意していて、現在のループを即座に抜けてそのループの直後の文に制御を転送する機能を持っている。サブルーチン内のループでreturn を使えば、入れ子になったループからも脱出することになる。多次元配列を入れ子になったループで検索している場合、若干複雑になる(「提案された制御構造」の章参照)。

以下の例はAdaを使ったものである。Ada は「ループからの早期脱出」と「途中にテストのあるループ」の両方をサポートしている。どちらもよく似ているが、コードを比較すればその違いがわかる。いずれにしても、汎用の制御構造であるif文との組み合わせによるものか、専用の制御構造によるものか、という違いでしかない。

withAda.TextIO;withAda.IntegerTextIO;procedurePrint_SquaresisX:Integer;beginRead_Data:loopAda.IntegerTextIO.Get(X);exitRead_DatawhenX=0;Ada.TextIO.Put(X*X);Ada.TextIO.New_Line;endloopRead_Data;endPrint_Squares;

Pythonbreak でループを早期脱出したか否かに依存して、実行されるブロックを指定できる。以下はその例である。

forninset_of_numbers:ifisprime(n):print"Set contains a prime number"breakelse:print"Set did not contain any prime numbers"

Python ではfor 文もwhile 文もこのようなelse 節を使うことができる。else 節は早期脱出が発生しなかったときのみ実行される。

ループ変化条件とループ不変条件

[編集]

ループ変化条件英語版ループ不変条件は、ループの正しさを表すのに使われる[7]

現実的には、ループ変化条件とは非負の初期値を持つ整数式である。変化条件はループを回るたびに減少しなければならないが、正しいループ実行の間は負の値になってはならない。ループ変化条件はループが終了するであろうことを保証するのに使われる。

ループ不変条件は、ループを回る前と各反復において真でなければならない表明である。すなわち、ループが正しく終了するには終了条件とループ不変条件が共に真でなければならない。ループ不変条件は、ループ実行中にループの具体的属性を監視するのに使われる。

Eiffelなどのプログラミング言語でループ変化条件とループ不変条件がサポートされている。JavaではアドオンであるJava Modeling Language (JML) という仕様で同様のものをサポートしている[8]

サブ言語としてのループ

[編集]

Lisp方言では、ループを記述するための多機能なサブ言語を提供することが多い。代表的な例としては、Common Lisp の LOOP が挙げられる[9]。初期の例としてはInterlisp の Conversional Lisp がある。

ループ機能の比較表

[編集]
プログラミング言語条件制御ループループ早期脱出継続繰り返しの再実行ループの再実行ループの正しさの保証
先頭途中末尾カウントコレクション汎用無限[※ 1]変化条件不変条件
AdaYesYesYesYes配列NoYes深い入れ子No
CYesNoYesNo[※ 2]NoYesYes深い入れ子[※ 3]深い入れ子[※ 3]No
C++YesNoYesNo[※ 2]Yes[※ 4]YesYes深い入れ子[※ 3]深い入れ子[※ 3]No
C#YesNoYesNo[※ 2]YesYesYes深い入れ子[※ 3]深い入れ子[※ 3]
Common LispYesYesYesYesYesYesYes深い入れ子Yes[※ 5]
EiffelYesNoNoYes[※ 6]YesYesNo1レベル[※ 6]NoNoNo[※ 7]整数のみ[※ 8]Yes
F#YesNoNoYesYesNoNoNo[※ 9]NoNo
FORTRAN 77YesNoNoYesNoNoNo1レベルYes
Fortran 90YesNoNoYesNoNoYes深い入れ子Yes
Fortran 95およびそれ以降YesNoNoYes配列NoYes深い入れ子Yes
HaskellNoNoNoNoYesNoYesNo[※ 9]NoNo
JavaYesNoYesNo[※ 2]YesYesNo深い入れ子深い入れ子No拡張機能[※ 10]拡張機能[※ 10]
JavaScriptYesNoYesNo[※ 2]YesYesNo深い入れ子深い入れ子No
OCamlYesNoNoYes配列、リストNoNoNo[※ 9]NoNo
PHPYesNoYesNo[※ 2][※ 11]Yes[※ 12]YesNo深い入れ子深い入れ子No
PerlYesNoYesNo[※ 2][※ 11]YesYesNo深い入れ子深い入れ子Yes
PythonYesNoNoNo[※ 11]YesNoNo深い入れ子[※ 9]深い入れ子[※ 9]No
REBOLNo[※ 13]YesYesYesYesNo[※ 14]Yes1レベル[※ 9]NoNo
RubyYesNoYesYesYesNoNo[※ 15]深い入れ子[※ 9]深い入れ子[※ 9]YesYes
Standard MLYesNoNoNo配列、リストNoNoNo[※ 9]NoNo
Visual Basic .NETYesNoYesYesYesNoYesループの種類毎に1レベルループの種類毎に1レベル
Windows PowerShellYesNoYesNo[※ 2]YesYesNo?Yes
  1. ^while (true) は構文としては無限ループ専用の構文ではないので、ここでは無限ループに含めていない。一方、for (式;;式) は無限ループ専用とみなしている
  2. ^abcdefghC言語のfor (init;test;increment) は汎用であり、カウント制御専用ではないが、カウント制御として使われることが多い。
  3. ^abcdefC、C++、C# での深い入れ子からの脱出は、ラベルとgoto文を使用する。
  4. ^C++11標準で、範囲に基づくforループが導入された。STLにはstd::for_each というテンプレート関数があり、STLのコンテナに対して各要素に単項関数を適用できる[10]。同様の機能はマクロを使っても実現可能[11]
  5. ^LOOP は独自の分岐構文を内包している
  6. ^abカウント制御ループは整数 interval によるイテレーションで実現される。早期脱出は exit に条件を追加することでなされる。
  7. ^Eiffelにはretry という予約語があるが、これはループ制御用ではなく例外処理用である。
  8. ^ループ変化条件は整数でなければならず、超限的変化条件はサポートしていない[1]
  9. ^abcdefghi深いブレイクを実現するには、例外処理を活用する必要がある。
  10. ^abJava Modeling Language (JML) が必要
  11. ^abcカウントループは例えばPythonのrange() を使って incrementing list や generator でシミュレートされる。
  12. ^オブジェクト群のイテレーションは PHP 5 で追加された
  13. ^while 関数を使用する(関数ではないが、関数だと誤解している者が多い)。
  14. ^ユーザーが汎用ループ関数を定義できる。
  15. ^ただし、標準ライブラリに無限ループを実現するloopメソッドが存在する。

構造化非局所制御フロー

[編集]

多くのプログラミング言語、特に動的なプログラミングスタイルを指向した言語では、「非局所制御フロー」の構造を持っている。これを使うと実行の流れは現在のコンテキストから離れ、事前に定義された場所から続行される。「条件」、「例外」、「継続」の3種類の典型的な非局所制御構造がある。

条件

[編集]

PL/I は標準で22種類の条件(ZERODIVIDE、SUBSCRIPTRANGE、ENDFILE など)をサポートし、これを発生(RAISE)させ、ONcondition action; で解釈することができる。プログラマは独自の条件を定義することもできる。

構造無しの IF 文のように action にはひとつの文しか書けないので、多くの場合 GOTO 文を使って制御フローを継続する必要がある。

しかし、実装によってはこれは空間と時間を無視できないくらい浪費する(特に SUBSCRIPTRANGE の場合)。多くのプログラマは条件を使わないようコードを書くことが多かった。

典型的な文法例:

ONconditionGOTOlabel

例外

[編集]
→詳細は「例外処理」を参照

最近の言語は例外処理を行う構造化された制御構造を備えている。

try{xxx1// この中のどこかで以下を使用するxxx2//     '''throw''' someValue;xxx3}catch(someClass&someId){// someClass の場合をキャッチactionForSomeClass}catch(someType&anotherId){// someType の場合をキャッチactionForSomeType}catch(...){// 既にキャッチされていない任意の値をキャッチactionForAnythingElse}

任意のcatch 節が上記の例では使用されている。D言語、Java、C#、Python ではtry 構造にfinally 節を追加することができる。try 部分を離れる際にはどういう理由であっても必ずfinally 節が実行されることが保証されている。これは処理を終了する際に何らかの高価な資源(オープン中のファイルやデータベース接続)を解放しなければならない場合に便利である。

FileStreamstream=newFileStream("logfile.txt",FileMode.Create);// C# の例try{returnProcessStuff(stream);// 例外を発生する可能性がある}finally{stream.Close();}

この例は非常に一般的であり、C# ではこのための特別な構文がある。

using(FileStreamstream=newFileStream("logfile.txt",FileMode.Create)){returnProcessStuff(stream);// 例外を発生する可能性がある}

上記の例のusing ブロックを離れるとき、コンパイラが自動的にstm オブジェクトを解放する。Pythonのwith 文やRubyのFile.open へのブロック引数も同様の効果がある。

このような言語はいずれも標準の例外を定義し、それらがどのような状況で発生するかを定義している。ユーザーは独自の例外を発生させることもできる(実際、C++ と Python は任意の型の throw と catch が可能)。

特定のthrow にマッチするcatch がない場合、マッチするcatch が見つかるまで入れ子構造を遡り、サブルーチン呼び出しを遡る。メインプログラムまで遡っても対応するcatch がない場合、プログラムは適切なエラーメッセージを出力して停止する。

AppleScriptスクリプト言語 は "try" ブロックにいくつかの情報を提供する。

trysetmyNumbertomyNumber/0onerrorenumbernfromftotpartialresultprif(e="Can't divide by zero")thendisplay dialog"You must not do that"endtry

継続

[編集]
→詳細は「継続」を参照

非局所制御フローの比較表

[編集]
プログラミング言語条件例外
AdaNoYes
CNoNo
C++NoYes
C#NoYes
Common LispYesNo
DNoYes
EiffelNoYes
HaskellNoYes
JavaNoYes
Objective-CNoYes
PHPNoYes
PL/IYesNo
PythonNoYes
REBOLYesYes
RubyNoYes
Visual Basic .NETYesYes
Windows PowerShellNoYes

提案された制御構造

[編集]

ドナルド・クヌースは1974年の論文 "Structured Programming with go to Statements"[12] でそれまでの制御構造でカバーされていない2種類の状況を提示し、それを実現する制御構造を例示した。他にも以下に示すような提案がある。

途中にテストのあるループ

[編集]

これは1972年にダールが提案した[13]

looploop       xxx1                           read(char);while test;whilenot atEndOfFile;       xxx2                           write(char);repeat;repeat;

もしxxx1 が省略されたら、テストが先頭にあるループとなる。もしxxx2 が省略されたら、テストが最後尾にあるループとなる。while が省略されれば無限ループとなる。このような一種類の制御構造で、必要な多くのタイプのループのパターンを表現できることが示されたことから、以降の言語ではこのような汎用性の高いループ構造を持つものもある(VBのDo...Loopなど)。ありうべき派生としてループ内に複数のwhile テストを配置することを許すことが考えられるが、その場合は後述のexitwhen の方が適切である。

一般に、任意のループ構造と、条件分岐とbreakを組み合わせて、同様のプログラムを書ける(これは要するに、どんな制御構造も、コンパイルされれば機械語では分岐命令になる、ということと同様のことを言っている)。

while (true) {    xxx1if (not test)break    xxx2}

Adaでは、上記のループ構造(loop-while-repeat)の代替として標準の無限ループ(loop-end loop)内でexit when節を使うことで同様の制御構造を実現できる(後述のexitwhen 文と混同しないよう注意されたい)。

withAda.Text_IO;withAda.Integer_Text_IO;procedurePrint_SquaresisX:Integer;beginRead_Data:loopAda.Integer_Text_IO.Get(X);exitRead_DatawhenX=0;Ada.TextIO.Put(X*X);Ada.TextIO.New_Line;endloopRead_Data;endPrint_Squares;

ループの命名(この例ではRead_Data)は必須ではないが、ループの入れ子で外側のループまで脱出させることができる。

複数早期脱出と入れ子ループからの脱出

[編集]

これは 1974年、Zahn が提案した[14]。ここではそれを若干修正したものを示す。

exitwhen EventAor EventBor EventC;       xxxexits       EventA: actionA       EventB: actionB       EventC: actionCendexit;

exitwhenxxx 内で発生しうるイベントを指定するのに使い、イベントはイベント名を文として使用すると発生する。イベントが発生すると対応するアクションが実行され、その後endexit 後の処理に移る。この制御構造はある状況を識別する部分と、その状況でとるべきアクションを明確に区別することができる。

exitwhen は C++ 言語のtry/catch 構造と概念的によく似ているが、サブルーチン呼び出しを超えたり任意の値を渡したりしないので、より効率的と思われる。また、コンパイラは指定されたイベントが全て発生する可能性があり、それらにアクションが対応しているかどうかをチェックできる。

以下の単純な例は2次元配列から特定の要素を取り出すものである。

exitwhen foundor missing;for I := 1to Ndofor J := 1to Mdoif table[I,J] = targetthen found;       missing;exits       found:   print ("item is in table");       missing: print ("item is not in table");endexit;

COMEFROM

[編集]
→詳細は「comefrom文」を参照

Datamation誌(1973年12月)に掲載された記事で[15]、R. Lawrence Clark は COME FROM 文を提案し、面白い例をいくつか提示した。それ自体は「GO TO論争に寄与する」と称したジョークであるが、ジャーゴンファイルの記事が指摘しているように[16]、たとえばFortranのDO文は「そこで指定した行番号のある行からそこに飛ぶ」という一種のCOMEFROMであることなど、制御構造の問題に面白い視点を与えるものではある。setjmp/longjmpと関連させた指摘もある[17]

COMEFROM文はINTERCALという難解プログラミング言語に実装された(INTERCALの実装者はジャーゴンファイルの編集者でもある)。

脚注

[編集]
  1. ^procedural programming
  2. ^imperative programming
  3. ^bit 編集部『bit 単語帳』共立出版、1990年8月15日、122頁。ISBN 4-320-02526-1 
  4. ^究極的には、端に達したら先頭に暗黙のジャンプをする、というだけの巨大なループ1個でも計算可能性を満たすことはできなくはないが(例えばHow to Make Zuse's Z3 a Universal Computer で使っている手法)現実的ではない。
  5. ^https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
  6. ^Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.
  7. ^Meyer, Bertrand (1991). Eiffel: The Language. Prentice Hall. pp. 129-131 
  8. ^Predicates and Specification Expressions in "JML Reference Manual"
  9. ^Common Lisp LOOP macro”. 2012年9月8日閲覧。
  10. ^for_each. Sgi.com. Retrieved on 2010-11-09.
  11. ^Chapter 1. Boost.Foreach. Boost-sandbox.sourceforge.net (2009-12-19). Retrieved on 2010-11-09.
  12. ^Knuth, Donald E. "Structured Programming with go to Statements" =ACM Computing Surveys 6(4):261-301, December 1974.
  13. ^Dahl & Dijkstra & Hoare, "Structured Programming" Academic Press, 1972.
  14. ^Zahn, C. T. "A control statement for natural top-down structured programming" presented at Symposium on Programming Languages, Paris, 1974.
  15. ^We don't know where to GOTO if we don't know where we've COME FROM. This (spoof) linguistic innovation lives up to all expectations. By R. Lawrence Clark* From DATAMATION, December, 1973
  16. ^http://catb.org/jargon/html/C/COME-FROM.html
  17. ^http://www.nurs.or.jp/~sug/soft/super/longjmp.htm#sec36

参考文献

[編集]
  • Hoare, C. A. R. "Partition: Algorithm 63," "Quicksort: Algorithm 64," and "Find: Algorithm 65." Comm. ACM 4, 321-322, 1961.

関連項目

[編集]

外部リンク

[編集]
https://ja.wikipedia.org/w/index.php?title=制御構造&oldid=107294723」から取得
カテゴリ:
隠しカテゴリ:

[8]ページ先頭

©2009-2025 Movatter.jp