C言語の関数に関する仕様は、細かな点でC++との違いがいろいろあります。C++ではできてC言語ではできないこともあれば、C言語ではできてC++ではできないこともあります。今回は、それらについて順番に解説していきます。なお、C言語のバージョンによって異なる内容もありますので、ここではあくまでもC89を対象にしていることを念押ししておきます。
関数の多重定義はできない
C++では、引数の型や個数によって、同名の関数を多重定義(オーバーロード)することができました。しかし、C言語ではそのようなことはできません。(可変個引数や関数原型なしの場合をのぞき)引数の型や個数が異なれば、別の名前にしなければなりません。
これは、C言語の関数は、C++においてC結合を用いた場合と同じだからです。すなわち、単に多重定義ができないだけでなく、仮に引数の型や個数に矛盾があったとしても、リンク時にエラーを検出できないことを意味しています。関数のシグニチャの整合性はプログラマの責任で保障しなければなりません。
なお、C言語では演算子の多重定義を行うこともできません。
省略時実引数は使えない
C++では、実引数を省略した場合にデフォルトで関数に渡される省略時実引数を指定することができました。しかし、C言語では省略時実引数を使うことができません。したがって、必要な実引数は、関数を呼び出す際に明示的に指定しなければなりません。
インライン関数は使えない
C++では、inline関数指定子を指定することで、関数のインライン置換をコンパイラに示唆することができました。しかし、C言語ではインライン関数を使うことはできません。C言語ではテンプレートを使うこともできませんので、ヘッダファイルで関数定義を記述することはC言語では皆無であると考えてよいでしょう。
仮引数並びを省略した場合の振る舞い
C++では、関数の仮引数並びを省略した場合、voidを指定したものとして扱われました。しかし、C言語では仮引数並びを省略すると、関数原型(プロトタイプ)がないものとみなされます。そして、関数呼出し式において、実引数の型や個数はまったくチェックされません。関数原型のない関数に渡された実引数は、可変個の実引数の場合と同様に、既定の実引数拡張が行われます。
関数宣言は必須ではない
C++では、多重定義を解決しなければならない事情もあり、関数を呼び出す際は、先立って関数原型(プロトタイプ)が必要でした。しかし、C言語では関数原型は必須ではありません。それどころか、まったく宣言のない関数をいきなり呼び出すことが可能です。関数原型がない場合、返却型は宣言された型に、仮引数並びは省略されたものとみなされます。関数の宣言自体がない場合、返却型はint型に、仮引数並びは省略されたものとみなされます。
0 1 2 3 4 5 6 7 8 9 |
double foo(); /* 関数原型なし */ int main(void) { foo(1, 2.0, "abc"); /* 関数原型のない関数の呼出し */ bar(1.0, 2L); /* 宣言のない関数の呼出し */ return 0; } |
ただし、可変個の実引数を受け取る関数を関数原型なしで呼び出した場合の動作は未定義になりますので注意が必要です。
関数原型はC言語にはもともとなかった仕様です。標準化の際にC++からC言語にバックポートされたものです。昔ながらのC言語の標準関数には、char型やshort型やfloat型を受け取るものがありませんが、これはもともと関数原型の仕様がなかったことが原因と考えられます。
分離形式
先ほど、関数原型はC++からバックポートされた仕様だと書きました。昔は関数原型の仕様がありませんでしたので、関数定義の際の仮引数の記述のしかたもちょっと違っていました。
0 1 2 3 4 5 6 7 8 |
int main(argc, argv) int argc; char *argv[]; { ... return 0; } |
上記のように、関数名の直後のカッコ内には仮引数名のみを並べ、関数本体のブロックの前に各仮引数の型を指定するための宣言を記述したのです。このような書き方を「分離形式」といいます。この仕様は、(主に互換性のために)標準Cでも有効です。古いCコンパイラとの互換性を保つ以外の理由で分離形式を使うことはまずありませんが、C言語ではこうした記述も可能だということは知っておくべきでしょう。
なお、分離形式で仮引数を記述した場合、これは関数原型にはなりません。こうした関数定義のあとで呼び出す場合、実引数の型や個数はチェックされませんし、既定の実引数拡張が行われることになります。
ちなみに、こうした分離形式に対して、C++と同じような関数原型を兼ねる仮引数の記述方法は「一括形式」といいます。