C言語の関数のプロトタイプ宣言の書き方

332, 2021-10-10

目次

C言語の関数のプロトタイプ宣言の書き方

C言語では関数のプロトタイプ宣言を書くことができます。
プロトタイプ宣言はモジュールで定義されている関数や、後方で定義されている関数を使いたい時に書きます。

C言語は後方参照で警告が出る言語のため、後方の定義の関数を参照したい場合はプロトタイプ宣言を書く必要があります(あるいはコンパイラを抑制するか)。
また、ライブラリの関数などはヘッダファイルに関数のプロトタイプ宣言を書いて公開するのが一般的です。

たとえばC言語の標準ライブラリであるstdio.hには関数のプロトタイプ宣言がたくさん書かれています。

C言語で関数を使う場合はこのプロトタイプ宣言が必要になる場合があります。
ですのでプロトタイプ宣言の書き方を覚えるのは非常に有用と言えます。

この記事ではC言語の関数のプロトタイプ宣言の書き方、そしてそれが必要な理由について具体的に解説します。

関数のプロトタイプ宣言を書く

C言語の関数のプロトタイプ宣言は、関数の定義からブロック文を除いたものです。
たとえば↓のような関数funcがあったとします。

void func(void) {
    puts("Hello, World!");
}

この関数のプロトタイプ宣言は↓になります。

// 関数funcのプロトタイプ宣言
void func(void);

カッコ以降の波カッコ({})が無くなってセミコロンになりました。
これが関数のプロトタイプ宣言です。

関数のプロトタイプ宣言の書ける場所

関数のプロトタイプ宣言はソースファイル、ヘッダーファイルの両方に書くことができます
また、関数の定義は一度しか書くことができませんが、関数のプロトタイプ宣言は何度でも書くことが可能です。

プロトタイプ宣言を書くときの注意点

関数のプロトタイプ宣言は関数の定義と同じでなければなりません
関数のプロトタイプ宣言と関数の定義が違う場合はコンパイラが警告を出力し、エラーになります。

たとえば↓のような関数の定義と宣言はエラーになります。

void func(int a);

void func(void) {
    puts("Hello, World!");
}

↑の場合、関数funcの定義とプロトタイプ宣言の引数の宣言が異なっています。
プロトタイプ宣言のほうは引数int aがあるのに、定義のほうはvoidになっています。
このようなコードをコンパイラに通すとGCCの場合↓のようなエラーになります。

warn.c:5:6: error: conflicting types for 'func'
編集注: エラー: 'func'のタイプが衝突しています
 void func(void)
      ^
warn.c:3:6: note: previous declaration of 'func' was here
編集注: ノート: 前の'func'の宣言はここです
 void func(int a);

関数の名前が同一で、返り値の型や引数の宣言が違う場合は↑のようなエラーになります。
この場合、コンパイルが通らなくなるので注意が必要です。

関数のプロトタイプ宣言はなぜ必要か?

関数のプロトタイプ宣言はなぜ必要になるのでしょうか?
わざわざ関数のプロトタイプ宣言を書く理由は?
結論から言うと、C言語は後方参照に警告が出る言語で、ワンパスのコンパイラで実装されているからです。
具体的に見ていきます。

C言語は後方参照に警告が出る言語

後方参照」とは、AとBという関数を順に定義した場合に、AからBを呼び出すことを言います。
つまり先に定義した(前方の)関数Aから、後に定義した(後方)の関数Bを呼び出すということです。

C言語はプロトタイプ宣言なしの後方参照はコンパイラが警告を出力します。
なぜかというとこれはC言語を実装しているコンパイラがワンパスだからです。

ワンパスのコンパイラ

ワンパス」とはコンパイラが一回の読み込みでソースコードから機械語を生成することを言います。
C言語のコンパイラはこのワンパスが前提になっています。

ちなみにツーパスのコンパイラもあります。こちらは二回読み込みます。
一般的には速度面でワンパスのコンパイラの方が優秀だと言われています。
特に大規模開発になると、コンパイラのコンパイル速度がもろにプロジェクトの進捗に影響するため、コンパイラはとにかく早い方がいいという風潮があります。
そのため古(いにしえ)の時代からC言語のコンパイラはワンパスで実装されています。

関数のプロトタイプ宣言が必要になるのはどんな時か?

C言語の関数のプロトタイプ宣言が必要になるのはどんな時でしょうか?
なぜプロトタイプ宣言を書く必要があるのか?
結論から言うとそれは↓のような時です。

  • 後方の関数を参照したいとき

  • モジュールを作ってヘッダーファイルを公開する

後方の関数を参照したいとき

関数のプロトタイプ宣言は前方で定義している関数から、後方で定義している関数を参照したい時に使われます。
たとえば↓を見てください。

void cat(void) {
    dog();
}

void dog(void) {
    puts("Wan Wan");
}

↑の場合、関数catが先に定義されています。
関数dogcatの後に定義されています。
これはcatが前方の関数で、dogが後方の関数と言うことになります。

↑のコードのままコンパイルするとGCCでは↓のような警告が出ます。

decl.c: In function 'cat':
decl.c:11:5: warning: implicit declaration of function 'dog' [-Wimplicit-function-declaration]
編集注: 関数'dog'の暗黙的な宣言です
     dog();
     ^
decl.c: At top level:
decl.c:14:6: warning: conflicting types for 'dog'
編集注: 'dog'のタイプが衝突しています
 void dog(void)
      ^
decl.c:11:5: note: previous implicit declaration of 'dog' was here
編集注: 前の'dog'の暗黙的な宣言はここです
     dog();
     ^

一応このコードは警告は出ますがコンパイルは通ります。
またcatからdogを呼び出すことも出来ます。

しかし、これはC言語のお作法的にはあまりよくありません。
コンパイラの警告を無くすには↓のように関数dogのプロトタイプ宣言をcatの定義より前に書きます。

void dog(void);

void cat(void) {
    dog();
}

void dog(void) {
    puts("Wan Wan");
}

↑のようにするとコンパイラの警告は出なくなります。

モジュールを作ってヘッダーファイルを公開する

関数のプロトタイプ宣言はヘッダーファイルに書かれることが多いです。
モジュールを作って、そのモジュールの関数を外部に公開したい場合があるとします。
C言語ではその外部に公開したい関数のプロトタイプ宣言をヘッダーファイルに書くのが一般的です。

ヘッダーファイルは例えば↓のようになります。

#pragma once

void mod_func_1(void);

int mod_func_2(int a, int b);

↑は「module.h」というヘッダーファイルのコードです。
module.c」にはこれらのプロトタイプ宣言されている関数の定義を書きます。

このモジュールを使いたい開発者は、自分のソースコードにmodule.hをインクルードします。
そうするとコンパイラが関数のプロトタイプ宣言を認識してくれます。
結果、このモジュールの関数を開発者は呼び出すことができるようになります。
もちろんmodule.cのコンパイルも必要ですが。

プロトタイプ宣言が無くてもモジュールの関数は呼び出すことはできますが、お作法的にはプロトタイプ宣言を読み込んだほうがいいということになります。

関数のプロトタイプ宣言を書けば循環参照もへっちゃら

ヘッダーファイルが循環して取り込めないとなったとき、そのヘッダーファイルに書かれているプロトタイプ宣言も読み込めないということになります。
そうすると、開発者が関数を使おうとするとコンパイラから警告が出ます。
こういったケースでは解決法はいろいろありますが、その1つがプロトタイプ宣言を使う方法です。

ヘッダーファイルの循環参照

ヘッダーファイルの循環参照とはたとえばヘッダーファイルAからヘッダーファイルBをインクルードしたとします。
そしてヘッダーファイルBもヘッダーファイルAをインクルードします。
このようになるとヘッダーファイルのインクルードが循環してしまいます。

ヘッダーファイルのインクルードの無限ループ自体は#pragma onceやインクルードガードなどで予防することができます。
しかし、ヘッダーファイルが取り込めないとプロトタイプ宣言も読み込めないのでコンパイラが警告を出してしまう事態になります。

プロトタイプ宣言で循環先の関数を使う

こういった場合は、ソースコード内で使いたい関数のプロトタイプ宣言を書いてしまいます。
たとえばライブラリの関数shout()を使いたい場合、↓のように自分のソースコード内にプロトタイプ宣言を書きます。

void shout(void);

そうするとコンパイラはプロトタイプ宣言を認識して警告を出さなくなります。

おわりに

今回はC言語の関数のプロトタイプ宣言について解説しました。
C言語ではプロトタイプ宣言はなにかとお世話になる宣言です。
マスターして開発に役立ててください。

ぜったいブロガー宣言

プロトタイプだったのか