関数プロトタイプ
関数プロトタイプ(英: function prototype)は、C言語やC++における関数の宣言であり、関数本体を省略して、関数名、アリティ、引数のデータ型、返り値のデータ型を示したもの。関数定義は関数が何をするかを示すが、関数プロトタイプはそのインタフェースを示すと考えることができる。
プロトタイプでは、引数の名前はオプションだが、その型指定は必須である。ただしCでは引数指定を空にすることで、未知(任意)とすることもできる。
例
[編集]例として、以下の関数プロトタイプを考える。
int fac(int n);
このプロトタイプから判ることは、関数名が "fac" であり、その引数は1個で整数型であり、返り値も整数型だということである。この関数を使いたい場合、プログラムのどこかで関数定義を提供しなければならない。
C/C++の場合、プロトタイプにおける型指定は、関数定義における型指定と互換性のある型であればよい。例えば、
void someFunction(const int* const array, const size_t length) {
...
}
という関数定義に対して、以下のようなプロトタイプ宣言はいずれも適合する。
void someFunction(const int* const array, const size_t length);
void someFunction(const int* array, size_t length);
void someFunction(const int array[], size_t length);
しかし以下のようなプロトタイプ宣言は型の互換性がないため、不適合となる(ポインタが指す先の値を変更できるint*
と、変更できないconst int*
には互換性がない)。
void someFunction(int* const array, const size_t length);
void someFunction(int* array, size_t length);
void someFunction(int array[], size_t length);
用法
[編集]コンパイラへの通知
[編集]歴史的な経緯[注釈 1]から、C89、C90およびC95までのC言語では、関数が事前に宣言されていない状況で、左括弧付きで式の中に現われた場合、その関数は暗黙のうちに int
を返すものと判断され、引数については何の想定もなされない。この場合コンパイラは、引数の型やアリティをコンパイル時にチェックできない。この暗黙の型指定の仕様が問題を引き起こすケースがある。以下のコードは、暗黙に宣言された場合の関数の振る舞いを示したものである。
#include <stdio.h>
/*
* もし、このプロトタイプ宣言があれば、コンパイラは関数呼び出し時の引数の不一致をエラーとして検出できる。
* しかしプロトタイプが省略された場合、コンパイラは int fac() であるとみなしてしまい、
* 実際の関数引数仕様との不一致があってもコンパイルエラーにはならない。
* 結果的にプログラマは引数指定のミスに気づかず、プログラム実行時に未定義動作を引き起こす。
*/
int fac(int n); /* プロトタイプ */
int main(void) { /* 関数呼び出し */
printf("%d\n", fac()); /* ERROR: fac の実引数がない */
return 0;
}
int fac(int n) { /* 呼び出される関数 */
if (n == 0) {
return 1;
}
else {
return n * fac(n - 1);
}
}
関数 "fac" が呼び出されたとき、コールスタックには1つの整数の引数が積まれていなければならない。プロトタイプが省略されると、コンパイラはそれをチェックできず、実行時に "fac" がスタック上の何らかの値(あるいはレジスタにある何らかの値)を引数として使うことになる(スタックの場合、スコープにない変数の値かリターンアドレスを使うことになる)。実引数が実際の関数インタフェースと一致していなかった場合、未定義動作を引き起こす。戻り値に関しても同様で、例えば本来はvoid*
を返すmalloc関数をプロトタイプ宣言なしで呼び出そうとすると、int
を返す関数とみなされてしまい、やはり未定義動作を引き起こす[1]。
C89では、関数プロトタイプの機能がC++から逆輸入される形で標準化された。関数プロトタイプを使えば、コンパイラに関数 "fac" が1つの整数引数をとることを知らせることができ、それによってコンパイラはこのようなエラーを検出できるようになる。
なお、関数の定義および宣言における戻り値の型指定を省略した場合は、int
と仮定される。また関数の定義における引数の型指定を省略した場合も、int
と仮定される。
/* 戻り値は int 型で、引数は未知 */
fac();
/* 戻り値は int 型で、引数 n は int 型 */
fac(n) {
...
}
以上のように、プロトタイプ宣言なしで関数を呼び出すコードや、戻り値の型指定を省略したコードは、たとえ標準規格としては適合であっても危険であるため、多くのCコンパイラは警告を出すようになっている[2]。C99では戻り値の型指定がない場合にint
と仮定する仕様や、関数プロトタイプがない場合に戻り値をint
と仮定する仕様が標準規格の文面から削除(廃止)され、関数は呼び出す前にプロトタイプ宣言が必須となった[3]。ただし、C99のプロトタイプ宣言では依然として引数を省略することもでき、例えば前述の例では以下のようなプロトタイプ宣言でも適合する。
int fac(); /* 引数は未知 */
一方、C++のほうはC++98として標準化された当初から完全なプロトタイプ宣言が必須となっている。またC++では、引数を省略した上記のプロトタイプ宣言は、下記のように「引数無し」と等価であるとみなされる。
int fac(void); /* 引数は無し */
ライブラリインタフェースの生成
[編集]関数プロトタイプをヘッダーファイルに置くことで、ライブラリのインタフェースを指定できる。関数シンボルがエクスポートされたライブラリのビルド済みバイナリとヘッダーを併せて配布し、任意のアプリケーションからリンクして利用することも可能となる。C/C++の各処理系における標準ライブラリは一般的にこの方法で配布されている。
クラス定義
[編集]C++では、関数プロトタイプはクラス定義にも使われる。クラスに属するメンバー関数などの宣言(または定義)をクラス定義のブロック内に記述する必要がある。
#include <string>
class MyClass {
std::string m_name;
public:
MyClass(); // デフォルトコンストラクタの宣言。
~MyClass(); // デストラクタの宣言。
// メンバー関数の宣言。
std::string getName() const;
void setName(const std::string& name);
};
クラス定義のブロック内に直接実装を記述したメンバー関数の内部で別のメンバー関数を呼び出す場合は、前方宣言は不要である。
#include <cmath>
class MyMath {
public:
static double calcLength(double x, double y) {
return std::sqrt(calcLengthSq(x, y));
}
static double calcLengthSq(double x, double y) {
return x * x + y * y;
}
};
言語バインディング
[編集]JavaのJNIや、.NETのP/Invokeでは、JavaやC#/VB.NETといったマネージ言語のコード側でメソッドのプロトタイプを宣言し、C/C++などで書かれたネイティブライブラリの関数シンボルを実行時にバインディング(関連付け)することができる。これにより、マネージコードからネイティブコードを利用することが可能となる。
脚注
[編集]注釈
[編集]出典
[編集]関連項目
[編集]参考文献
[編集]- Kernighan, Brian W.; Ritchie, Dennis M. (1988年), The C Programming Language (2nd ed.), Upper Saddle River, NJ: Prentice Hall PTR, ISBN 0131103628