プログラミングにおけるサブルーチン: subroutine)は、プログラム中で意味や内容がまとまっている作業をひとつにまとめたものである。サブプログラムあるいは副プログラム: subprogram[1]とも呼ばれ、単に「ルーチン」(: routine)と呼ばれることもある。プログラミング言語によっては、関数: function)やプロシージャあるいは手続き: procedure)とも呼ばれる。

概説

繰り返し利用されるルーチン作業モジュールとしてまとめたもので、呼び出す側の「主」となるもの(メインルーチン)と対比して「サブルーチン」と呼ばれる。

プログラムのソース中で、繰り返し現れる処理をサブルーチン化することで、可読性や保守性を高く保つことができる。繰り返し現れる処理でなくても、意味的なまとまりを示すためにサブルーチン化することもある。また、キャッシュのような階層的メモリの設計を持つコンピュータ(現在のパソコンワークステーションなどほぼすべて)では、よく使われるサブルーチンがキャッシュに格納されることで高速な動作を期待できる。

呼び出しと入出力

サブルーチンが結果として値を返す場合、その値は戻り値(もどりち)または返り値(かえりち)と呼ばれる。

プログラミング言語によっては、サブルーチンに相当する処理のまとまりを、「結果として値を返すもの」と「処理だけを行ない値を返さないもの」に分類・区別することがある。この区分はプログラミング言語の仕様で定められるため、言語により区分や名称が異なる。例えば、Pascalでは戻り値があるものを関数と呼び、戻り値がないものを手続きと呼ぶ。C言語ではいずれも関数と呼ぶ。ALGOLではいずれも手続きと呼ぶ。これらは慣習的なものであり、手続き型プログラミング関数型プログラミングといったプログラミングスタイルあるいはプログラミングパラダイムの分類とは関係がない。サブルーチンが属するスコープなどの特性によって名称を変えているものもあれば、特に区別せず同じ名称を与えているものもある。

日本の情報処理推進機構 (IPA) が運営している基本情報技術者試験で使われる疑似言語では、サブルーチンを戻り値の有無により「関数」と「手続」(てつづき)に分類している。戻り値があるほうが関数であり、戻り値が無いほうが手続である[1]。これはPascalの慣習と同じだが、もともとアルゴリズムの記述にはALGOLやPascal系の構文をもとにした疑似言語が使われることが多かった、という事情もある[2]

なお、サブルーチンは呼び出し時に入力として引数(ひきすう)を受け取り、サブルーチンの中で宣言された「仮引数」と呼ばれる変数を通じて内部処理に使うことができる。引数の渡し方には、一般的なものとして以下の2つがある[3]

  • 実引数を渡すときにその値だけを渡して、記憶場所(アドレス)は渡さない方法を「値呼び出し」または「値渡し」という。値呼び出しで変数を渡す場合、メインルーチンとは別にサブルーチン側で変数の記憶領域を確保し、そこにいったん値(データ)をコピーする。そのため、サブルーチン側で変数の値を変更しても、メインルーチンに戻ったときには元の値のままになる。
  • 実引数を渡すときにその記憶場所(アドレス)を渡す方法を「参照呼び出し」または「参照渡し」という。参照呼び出しで変数を渡す場合、メインルーチンとサブルーチンとで、変数の記憶領域が共有される。そのため、サブルーチン内でその変数に対して行なった変更操作は、メインルーチンに戻ってもそのまま反映される。

一般的には戻り値によって単一の結果を出力として返すが、参照呼び出しを使うことによって複数の結果を出力として返したり、受け渡しの際のデータコピーのコストを低減したりすることもできるようになる。

なお、Pascalでは関数/手続きの内部に、それらの中でのみ呼び出し可能な別の関数/手続きを記述することもできる。関数内関数やローカル関数などと呼ばれることもある。

一般的にサブルーチンにはプログラムソースコード上で識別可能な何らかの名前が付けられ、呼び出しの際にはその名前をもとに参照されるが、名前を持たないサブルーチン(無名関数、ラムダ式)を定義できる言語もある。

通例、サブルーチンとその呼び出し元の間での引数および戻り値の受け渡しや、サブルーチンが終了したときに呼び出し元にフロー制御を戻すための復帰位置(リターンアドレス)の一時記憶、サブルーチン内のローカル変数の割り当てには、コールスタックやレジスタが使われる。引数がスタックに積まれる順序やメモリレイアウトなどの呼び出し規約は、プログラミング言語やプロセッサ (CPU) のアーキテクチャにも依存するが、コンパイラ固有の修飾子(キーワード)によって指定できる環境もある[4][5]x64ARMのように、特定の条件を満たす引数についてはスタックではなくレジスタを経由して受け渡しされるアーキテクチャもある[6][7]

環境によっては、呼び出し規約を規定し、また引数と戻り値のデータ型を基本数値型やポインタ型などのPOD英語版型に限定することで、異なる言語間で相互運用可能なABI互換性のあるサブルーチンを定義することができる。例えばCで書かれた関数をダイナミックリンクライブラリ (DLL) や共有ライブラリ (so) にエクスポートし、Pascalの関数または手続きとして呼び出したりすることも可能となる。別の例として、Microsoft Excelでは、C/C++やPascalやVisual Basicなどを使ってDLLに実装された関数を、Visual Basic for Applications (VBA) のコードモジュールからプロシージャとして呼び出したり、ワークシートから直接利用したりすることができる[8][9]

数学の関数との違い

引数をとり、値を返すという、数学における関数との類似性から、言語によっては関数 (function) とも呼ばれるわけであるが、一般にプログラミング言語における関数は、数学におけるそれとは以下のような点が異なる。

  • 引数が同じでも状況に応じて戻り値が異なる(状態を持つ)
  • 関数の処理の実行によってシステムに変化が発生する(副作用を持つ)
  • (「関数」と「手続き」を区別しない言語では)戻り値が存在しない場合がある

これに対し、関数型言語では状態や、プログラム自身に影響するような副作用をもたないことを基本とするなど、数学の関数に近い性質を持つ。特に純粋関数型言語では数学の関数と同等であり、同様の性質(参照透過性など)を持つことが利用される。

オブジェクト指向言語の場合

多くのオブジェクト指向プログラミング言語では、何らかのオブジェクトあるいはクラスに属するサブルーチンは、メソッドと呼ばれている。Cから発展し、Simulaの影響を強く受けたC++では、クラスや構造体に属する関数はメンバー関数と呼ばれている。

JavaC#など、サブルーチンはクラスや構造体などの型に属するメソッドもしくはメソッド内のローカル関数として記述しなければならず、どこにも属さない関数(フリー関数)は定義できないオブジェクト指向言語もある。一方、C++Swiftのように、必ずしもクラスや構造体に属する必要はなく、名前空間スコープに関数を直接定義できるオブジェクト指向言語もある。

歴史

サブルーチンという考え方は、ことさら新しいものではない。アルゴリズムなどにおいて、問題を部分問題に切り分けて解くという分割統治法はコンピュータ以前からあり、コンピュータプログラミングについても、EDSACのプログラミングについて出版された、この分野の世界最初の書籍とされる The Preparation of Programs for an Electronic Digital Computer(1951)においても part one, chapter 2 がサブルーチンに関する章である。

各プログラミング言語におけるサブルーチン

おおむね歴史が古い言語から説明する。

FORTRAN

Fortranでは、値を返すサブプログラムは関数(function)、値を返さないサブプログラムはサブルーチン(subroutine)と呼ばれる。Fortran 90以降の関数はPURE属性により副作用を持たないことを明示できる。

LISP

LISPでは関数と呼ばれることが多く、Common Lispでも関数と呼ぶ。しかしSchemeの仕様では手続きという用語を使っている。なおLispにはサブルーチンをマクロで実装するという重要な手法もある。

BASIC

古典的なBASICでは、GOSUB 命令によるサブルーチンがあった。インタプリタは GOSUB 命令を見つけると、GOSUB 命令の終わりの場所(アドレス)をインタプリタ内のスタックにプッシュして保存し、命令で指定された行に飛び、実行を続ける。その後、実行中に RETURN 命令を見つけると、スタックから先ほど保存しておいた呼び出し元の場所をポップして取り出し、そこに飛び、GOSUB 命令の次の命令から実行を再開する。サブルーチンを作成したい場合は、ユーザーは「この行からこの行まではサブルーチンとする」と決めてプログラムを作成した。以上のように「RETURN できる GOTO」でしかない(引数も返り値もローカル変数もない)ため、(グローバル)変数を経由する、配列をユーザースタックとして使うなど、技巧を必要とした。比較的高機能な実装では、DEFFN 命令により、式一個で記述できる範囲という制限ながらも、引数と返り値のあるユーザー定義関数の追加(拡張)が行えるものもあった。

Pascal

Pascalではfunctionおよびprocedureという予約語を使って宣言する[注釈 1]

C言語

カーニハンリッチーによる解説書『プログラミング言語C』、いわゆる「K&R」には、C言語の関数はPascalの「手続き」や「関数」に相当すると書かれてある[10]。つまりC言語では戻り値の有無にかかわらず「関数」(function) と呼んでいる。

関数の定義の書式は次の通りである[10]

<戻り値の型> <関数名>( <引数> )
{ 
  関数内の処理の記述
}

C言語では歴史的な理由から、値を返さない関数を宣言および定義する場合は、関数の型(返戻値の型)を書く場所にvoidvoid型)と書く[注釈 2]

関数の宣言が複数ある場合、どの順番で書いても良いし、複数のファイルに分けて書いても良い[10]。ただし、ひとつの関数を複数のファイルに分けて書いてはいけない[10]

なお、K&RのCはANSI C(C89)として標準化される前の古い仕様に基づいており、Cの歴史を知るうえでは重要だが、2023年現在はISO/IEC 9899規格を参照することが望ましい。

Perl

Perlでは、ユーザー定義のものはサブルーチンであるが、引数を渡すことができ、値を返すこともできる。サブルーチン内からは、渡された引数には特殊変数からアクセスし、値を返すにはreturn文[注釈 3]を使う。

Visual Basic

Visual Basic (VB)、Visual Basic for Applications (VBA)、Visual Basic .NET (VB.NET) では、サブルーチンをプロシージャと総称しており、値を返すプロシージャを「Functionプロシージャ」、値を返さないプロシージャを「Subプロシージャ」と呼ぶ。さらにプロパティを定義するための構文として「Propertyプロシージャ」がある[11][12]

Microsoft Excelにおける関数は、主に、計算をしたり、データの検索や集計をしたり、表示を変換したりするものである。合計値を求めるSUM関数や、平均値を求めるAVERAGE関数、条件演算子に相当するIF関数など、100を超える組み込みの「ワークシート関数」が存在する。一方、VBAコードからのみ利用可能な「VBA関数」もある。例えばIIf関数[13]はIF関数に似た働きをするが、ワークシート上の数式内では利用できず、VBAからのみ利用可能である。ワークシート関数およびVBA関数はいずれもユーザー定義の関数を登録して利用することもできる。

再帰呼び出し

あるサブルーチンfの定義中でf自身を再度呼び出すことを再帰呼び出し(さいきよびだし、recursive function call)という[14][1]。再帰呼び出しを使うと、ある処理で得られた値に対しさらに同じ処理を何度も繰り返すような場合に簡潔な記述にすることができる[15]。たとえば階乗の計算をするために使ったり[1]ファイルシステムのような木構造の探索に使ったり[16]オセロソフトチェスソフト将棋ソフト囲碁ソフトなどの「先読み」に使ったりできる[注釈 4][17][18][19][20]

なお、一般的なコンピュータおよびプログラミング言語では、サブルーチン呼び出しの際はコールスタックに引数やローカル変数などの領域が確保されるが、パーソナルコンピュータのような比較的リソースに余裕のある環境であっても、このコールスタックの容量は既定でスレッドごとに数MiB程度に設定されており、サブルーチンの呼び出し階層が深すぎるとスタックオーバーフローを引き起こす場合がある。特に再帰呼び出しは呼び出し階層が深くなりがちで、再帰回数が多すぎるとスタックオーバーフローを引き起こしやすい。

脚注

関連項目

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.