C言語編 第10章 関数プロトタイプ

この章の概要

この章の概要です。

main関数の記述位置

前章のサンプルプログラムでは、自作関数を先頭近くに記述し、main関数を下の方に記述していました。 これは、このような順番で記述しないと、コンパイルがうまくいかないためです。 それはなぜなのか考察してみましょう。 実際に、main関数を上の方にもってくると、次のようになります。

#include <stdio.h>

int main(void)
{
	double base, height, area;


	base   = 3.0;
	height = 5.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 7.0;
	height = 4.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 4.4;
	height = 3.6;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	return 0;
}

/*
	三角形の面積を求める。
	引数
		base:	底辺の長さ。
		height:	高さ。
	戻り値
		面積。
*/
double calcAreaOfTriangle(double base, double height)
{
	return ( base * height / 2 );
}

このプログラムを、VisualC++2017 でビルドしてみると、次のようなエラーメッセージが表示されます。

warning C4013: 関数 'calcAreaOfTriangle' は定義されていません。int 型の値を返す外部関数と見なします。
error C2371: 'calcAreaOfTriangle' : 再定義されています。異なる基本型です。

コンパイラによっては、エラーにはならなかったり、警告にはならなかったり、個数が違ったりと色々と違いはあるでしょうが、 いずれにしても、意図通りの動作にはなりません。

何がエラーで、何が警告になるかは、コンパイラの裁量次第であり、C言語の規格上は明確には定められていません。 ですから、コンパイラが何も文句を付けないからといって、そのプログラムに問題が潜んでいないという保証はありません。 なお、警告だけならば実行することができるコンパイラが多いでしょうが、警告も安易に無視してはいけません。

ここで問題なのは、「関数 'calcAreaOfTriangle' は定義されていません」の部分です。 この警告は 10行目で出ているので、main関数から calcAreaOfTriangle関数を呼び出している部分ですが、 要するに、この位置からは calcAreaOfTriangle関数の存在が見つけられないのです。 なぜ見つけられないかというと、「10行目よりも手前に calcAreaOfTriangle という単語が登場していないから」です。

こういう場合、コンパイラは勝手に、戻り値がint型で、引数の個数や型が不確定な calcAreaOfTriangle という関数があるものと解釈します。 それが先ほどのエラーメッセージにある「int 型の値を返す外部関数と見なします」の意味です。 本当は引数が double型2つで、戻り値も double型のはずなのに、間違った解釈をしてしまうことになり、 仮にこのプログラムが実行できたとしても、その動作は不可解なものになります。

これ以外にエラーや警告が出ていても、それは副次的に発生したものでしょう。 問題の根本は、calcAreaOfTriangle関数の存在が見つからず、勝手な解釈を行ってしまう点にあります。

この問題を解決するための1つの方法として、main関数を下の方に移動させていた訳ですが、実は望ましい解決策は別にあります。 それが、この章のテーマである関数プロトタイプです。

関数プロトタイプ

関数プロトタイプは、関数名・引数・戻り値の組み合わせを、関数本体と別に記述しておき、 このような関数が存在しているのだということを宣言するものです。

まずは、先ほどのプログラムを、関数プロトタイプを使って書き換えてみます。

#include <stdio.h>

double calcAreaOfTriangle(double base, double height);  /* 関数プロトタイプ */


int main(void)
{
	double base, height, area;


	base   = 3.0;
	height = 5.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 7.0;
	height = 4.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 4.4;
	height = 3.6;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	return 0;
}

/*
	三角形の面積を求める。
	引数
		base:	底辺の長さ。
		height:	高さ。
	戻り値
		面積。
*/
double calcAreaOfTriangle(double base, double height)
{
	return ( base * height / 2 );
}

実行結果:

底辺:3.000000  高さ:5.000000  面積:7.500000
底辺:7.000000  高さ:4.000000  面積:14.000000
底辺:4.400000  高さ:3.600000  面積:7.920000

main関数よりも手前に、1行追加しただけです。 この追加された宣言により、先ほどのエラーメッセージはなくなり、問題なく実行できるようになります。 この追加された行が、関数プロトタイプ(プロトタイプ宣言関数原型などとも呼ばれます)です。

今度のプログラムでは、main関数から calcAreaOfTriangle関数を呼び出す箇所から、calcAreaOfTriangle関数の存在が関数プロトタイプという形で見つけられます。 この関数プロトタイプには、「calcAreaOfTriangle という名前の関数があり、引数は double型が2つで、戻り値は double型である」 という情報が含まれているので、コンパイラはそのように正しく解釈できます。


関数プロトタイプは、関数の本体に記述する内容とまったく同じことを書けばいいので、コピー&ペーストするのが確実です。 末尾にはセミコロンを置いて終端を表します。 (プロトタイプ側と、本体側とで内容が違っていたら、無意味であるばかりか、更に不可解な結果を招きます。 こういうところでは、素直にコピー&ペーストするべきです)。

関数プロトタイプを記述する位置ですが、通常、ソースファイルの先頭付近で、#include よりは後に書くことになります。 他の位置に記述することも可能ですが、その場合の意味を正確に理解しているのでなければ、やめた方が無難です。 (いつでもそうですが、自分が何をしているのか理解せずにプログラムを書くのは危険です)。

関数プロトタイプの本来的な効果は、引数や戻り値の型が正しいかどうかを、コンパイラに厳密にチェックしてもらえるという点にあります。 決して、main関数を上の方に書けるようにするための機能ではありません。

関数プロトタイプは、安全なプログラムを書くにあたって必須とも言える機能です。 この機能が存在しなかった頃のよほど古いC言語のプログラムを保守するような場合を除き、 関数プロトタイプを使わない理由はまったくありません
なお、main関数に関数プロトタイプは不要です。

C++ では、関数プロトタイプは必須です。

C99 (関数プロトタイプの必須化)

C99 では、関数プロトタイプの使用は必須となりました。


練習問題

問題@ 円周の長さを渡すと、その円の半径を返すような関数を、関数プロトタイプも含めて自作して下さい。 このとき、引数と戻り値の型は double型とします。

問題A 問題@で作成した関数の戻り値を、int型の変数で受け取ろうとするとどうなりますか。


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2015/8/15 VisualC++ 2015 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2013/4/20 C99 で関数プロトタイプが必須である旨の記述を、別の項に分離。

'2012/9/17 VisualC++2012 でエラーメッセージの確認をし直した。

'2010/5/1 VisualC++2010 でエラーメッセージの確認をし直した。

'2009/3/17 「この章の概要」を追加。

'2009/2/25 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ