| 関数の型情報と関数プロトタイプ | 関数プロトタイプの書き方 | 暗黙の型情報 | ライブラリ関数の関数プロトタイプ | 演習問題 |
一般にC言語で関数を実行する際には, その関数を定義するときに指定した型の引数を渡し, それらの値を用いて何らかの処理を実行した結果が, 同様に関数の定義で指定した型の値(戻り値)として戻ってきます. たとえば下のように定義された関数addではdouble型の二つの引数が渡されて, それらを足した結果としてdouble型の値が戻ってきます.
double add(double a, double b) { return a+b; }
この関数を使ったプログラムは例えば以下のようになります.
#include <stdio.h> double add(double a, double b) { return a+b; } int main() { printf("answer=%f\n", add(3.0,4.0)); return 0; }
C言語ではプログラムをコンパイルするとき, 引数として渡す値や関数から戻されるはずの値が 関数の定義のときに指定した型に合致しているかどうかをチェックします。 これがうまく適合していないとコンパイル時にエラーとなります。 しかしこのとき,プログラム上で関数を呼び出す位置より前に 関数が定義されていないとコンパイラはその関数の 型に関する情報を知ることが出来ません.
上のプログラムではaddを定義した後にmainでaddを使っていますので, コンパイラはaddの定義に与えられた引数や戻り値の型の情報と照らし合わせて, mainの中でaddに引数として渡す値の型やaddから戻るとされている値の型が それらに合っているかどうかをチェックをすることが出来ます.
しかしこれから複雑なプログラムを書くようになると, プログラム上でこのような情報を 知ることが出来ない位置で関数を使う必要が出てきます.
例えば,上のプログラムでmainとaddの位置を入れ替えてみましょう.
#include <stdio.h> int main() { printf("answer=%f\n", add(3.0,4.0)); return 0; } double add(double a, double b) { return a+b; }
このプログラム(test2.c)をコンパイルすると以下のようなエラーがでます.
test2.c:10: error: conflicting types for 'add' test2.c:6: error: previous implicit declaration of 'add' was here
このようにコンパイルが失敗するのは 関数addの定義より前にaddが使われているために, addの型のチェックがうまくできないためです (このエラーメッセージの意味については後ほど説明します).
そこでC言語では関数の定義とは別に,関数についての このような情報を与えるための宣言を行う方法が用意されています. それが関数プロトタイプです (これは関数の「原型」というような意味です). 関数プロトタイプを使うと上のプログラムは次のように書き換えることができます.
#include <stdio.h> double add(double fst, double snd); int main() { printf("answer=%f\n", add(3.0,4.0)); return 0; } double add(double a, double b) { return a+b; }
このようにaddを使用するmainの前にaddの関数プロトタイプ
double add(double fst, double snd);
を挿入しておくことにより, コンパイラはaddがdoubleを返す関数であることを事前に知ることができるので, printfの中の式でその戻り値をdoubleとして扱っているところで 型が一致していることを,この情報を使ってチェックすることができます. よってこのプログラムは正しくコンパイルし実行することができます.
このような例からわかるように, 関数プロトタイプの一般的な形は以下のようになります (2引数の場合).
戻り値型 関数名(引数型1 仮引数名1, 引数型2 仮引数名2);
ただし,関数プロトタイプの仮引数名は, その関数の定義で用いた仮引数名と異なっていても構いませんし, 仮引数名を省略しても構いません. 例えば,上の例で次のような関数プロトタイプを使っても構いません.
double add(double, double);
こちらの方がシンプルですが,上手に仮引数名を付ければ読むときに 引数の意味がわかりやすくなるという利点もありますので, これらの書き方は必要に応じて使い分けましょう.
次に,関数定義や関数プロトタイプで いくつか特別に考えなければない場合があります. 一つめは関数に戻り値がない場合です. C言語ではしばしばこのような関数を使いますが, そのときは戻り値型としてvoidと書きます. 二つめは関数に引数がない場合です.このような場合は引数型として やはりvoidと書きます (この場合は仮引数名は意味がないので書きません). 従って例えば,引数もなく戻り値もない 関数fooのプロトタイプは以下のようになります.
void foo(void);
さらに,C言語にはprintfのように引数の数が可変の関数もあります. そのような関数のプロトタイプは...を用いて次のように書きます.
int printf(char *format, ...);
なお,古いプログラムでは次のように関数プロトタイプの引数の部分が 書かれていないこともあります.
double add();
これは以前のC言語で用いられていた宣言の仕方で 関数宣言と呼ばれます. この場合は上のvoidと違って引数の有無は不明と見なされるので 引数に関する型のチェックは行われません. 当然その分エラーが起こる可能性は高くなります.皆さんが書く場合は 特に必要がない限りは関数プロトタイプを用いるようにしましょう.
実はdoubleの代わりにintを用いた次のようなプログラムは 関数プロトタイプがなくてもコンパイルし実行することができます.
#include <stdio.h> int main() { printf("answer=%d\n", add(3,4)); return 0; } int add(int a, int b) { return a+b; }
これはC言語では関数の型の情報が得られないときには 戻り値の型は暗黙にintと仮定するという約束があるために, たまたま型がintで一致したためです. しかしこれはたまたま一致しただけなので, 皆さんは関数プロトタイプを書く習慣をつけましょう.
なおこれを踏まえれば,以前のdoubleを用いた時のエラーメッセージは 「この暗黙の仮定のintとaddの定義に書かれているdoubleが一致していない」 ということを意味していたことが理解できるでしょう. 英語ですがもう一度読んでみてください.
C言語でプログラムを開発する場合には 入出力関数や数学関数など様々なライブラリ関数を用いますが, それらについても当然ながら型についての情報を どこかから得る必要があります. この講義のはじめにインクルードファイルについて学びましたが, 実際にはライブラリ関数の関数プロトタイプはインクルードファイルに まとめて書かれたものがあらかじめ用意されており, それをライブラリ関数を使用する前に(ソースファイルの先頭などで) インクルードするのが普通です.
例えばman sinでマニュアルをみると,以下のように ライブラリ関数sinの用法とともに, 使用すべきインクルード指令(この場合は#include <math.h>) が書かれています.
SYNOPSIS #include <math.h> double sin(double X);
ライブラリ関数の使用は,プログラムをいくつかの部分に分けて開発する 分割コンパイルという手法の一種に当たりますので, ライブラリ関数の関数プロトタイプは実際にはその点も考慮して書かれています. 詳しくは後の章でそれらについて説明するときに述べることにします.
A. 次のように用いられている関数fとgの関数プロトタイプを書きなさい.
char a[10]; int i; int *p; ..... i=f(a); p=g(); .....
B. 以下のプログラムはpos, neg, mainの三つの関数の順序を どのように変えてもコンパイルに失敗します.その理由を述べなさい. また,正しくコンパイルして実行できるようにプログラムを変更しなさい. ただし関数の内容に変更を加えてはいけません.
/* arctan xのx=1における展開形 */ /* pi/4 = 1 - 1/3 + 1/5 - 1/7 + ... + 1/(4k+1) - 1/(4k+3) + ... */ #include <stdio.h> #define COUNT 300 double pos(double n) { return neg(n - 1) + 1 / (4 * n + 1); } double neg(double n) { if (n < 0) return 0; else return pos(n) - 1 / (4 * n + 3); } int main(void) { printf("pi = %f\n", 4 * pos(COUNT)); return 0; }