C言語のきほん|関数プロトタイプ宣言の書き方と役割

関数の情報を先に伝えて、Cプログラムを安心して組み立てよう

C言語では、プログラムは上から順番に読まれていきます。
そのため、まだ登場していない関数を途中で呼び出そうとすると、コンパイラは「その関数はどんな返却値を返すのか」「引数はいくつ必要なのか」が分からず、正しく確認できません。

そこで登場するのが、関数プロトタイプ宣言です。
これは、関数の本体を書く前に「この関数はこの形で使います」と先に知らせておくための宣言です。

関数プロトタイプ宣言を理解すると、

  • なぜ関数の情報を先に書く必要があるのか
  • 関数定義との違いは何か
  • 引数や返却値をどう正しく書けばよいか

といった大切なポイントがすっきり見えてきます。

関数を使ったプログラムは、処理を部品のように分けて書けるので、見やすく、直しやすく、再利用しやすくなります。
その第一歩として、まずは関数プロトタイプ宣言の書き方と役割をしっかり押さえていきましょう。

関数プロトタイプ宣言とは

関数プロトタイプ宣言とは、関数の本体を書く前に、その関数の名前・引数・返却値の型を先に知らせる記述です。

書き方は次のとおりです。

文法:関数プロトタイプ宣言

返却値型 関数名(引数型 仮引数名の並び);

たとえば、整数を受け取り、整数を返す display_square という関数なら、次のように書けます。

int display_square(int number);

ここで大切なのは、行末にセミコロンが付くことです。
このセミコロンがあることで、「これは関数の本体ではなく、宣言だけです」という意味になります。

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

C言語のコンパイラは、基本的にソースコードを上から順番に読んでいきます。
そのため、main 関数の中である関数を呼び出していても、その時点でまだその関数の情報を見ていなければ、正しくチェックできません。

たとえば、main 関数の中で message_count 関数を呼び出しているのに、その関数定義がもっと下にあるとします。
このとき、関数プロトタイプ宣言がなければ、コンパイラは次のような点を事前に確認しにくくなります。

確認したい内容コンパイラが知りたいこと
返却値の型int を返すのか、void なのか
関数名どの関数を呼び出すのか
引数の数1個なのか、2個なのか
引数の型int なのか、double なのか

つまり、関数プロトタイプ宣言は、関数を安全に呼び出すための設計図の見出しのようなものです。
まだ本体は書いていなくても、先に仕様だけ伝えておくことで、コンパイラは安心して読み進められます。

関数定義との違い

関数プロトタイプ宣言と関数定義は、見た目が少し似ています。
でも、役割ははっきり違います。

項目関数プロトタイプ宣言関数定義
役割関数の情報を先に知らせる実際の処理を書く
セミコロン付ける付けない
関数本体ないある
波かっこないある

たとえば、次の2つを見比べると違いが分かりやすいです。

int message_count(int times);

これは宣言です。
一方で、次は定義です。

int message_count(int times)
{
    return times * 2;
}

こちらは、波かっこの中に実際の処理が書かれているので、関数定義です。

関数プロトタイプ宣言の形を丁寧に見る

次の宣言を例に、それぞれの意味を確認してみましょう。

int repeat_count(int number);

この1行には、次の情報が含まれています。

要素意味
返却値型int関数が呼び出し元へ返す値の型
関数名repeat_count関数の名前
引数型int受け取るデータの型
仮引数名number関数の中で使う引数の名前

ここで特に大事なのは、プロトタイプ宣言には引数の型が必要だという点です。
型を書くことで、呼び出し側が正しい値を渡しているかをコンパイラがチェックしやすくなります。

なお、仮引数名は省略して、次のように書かれることもあります。

int repeat_count(int);

これでも意味は通じますが、学習段階では、

int repeat_count(int number);

のように仮引数名まで書いたほうが分かりやすいです。

サンプルプログラムで流れを確認しよう

あいさつメッセージを表示する回数を求めるプログラムです。

main 関数から message_repeat 関数を呼び出し、渡した数を2倍にした結果を受け取る例です。

ファイル名:13_4_1.c

// あいさつを表示する回数を求めるプログラム
#include <stdio.h>

int message_repeat(int count);

int main(void)
{
    int base_count = 3;   // 基本回数

    // 表示回数を計算する
    int total_count = message_repeat(base_count);

    printf("あいさつは%d回表示します。\n", total_count);

    return 0;
}

// 表示回数を2倍にする関数
int message_repeat(int count)
{
    int result = count * 2;
    return result;
}

実行結果例

あいさつは6回表示します。

このプログラムの流れは、とても大事です。

まず、先頭で

int message_repeat(int count);

と書いています。
これが関数プロトタイプ宣言です。

そのあとで main 関数が登場し、main の中で message_repeat(base_count) を呼び出しています。
この時点でコンパイラは、すでに message_repeat という関数の情報を知っています。
だから、「int 型の値を1つ受け取り、int 型の値を返す関数なんだな」と理解したうえでチェックできます。

そして、最後に実際の関数本体が書かれています。

このプログラムのどこが関数プロトタイプ宣言なのか

先ほどのプログラムを、役割ごとに分けて見ると理解しやすくなります。

部分コード役割
関数プロトタイプ宣言int message_repeat(int count);関数の情報を先に知らせる
関数の呼び出しint total_count = message_repeat(base_count);main から関数を使う
関数本体int message_repeat(int count) { ... }実際の処理を書く

この3つの違いを整理しておくと、今後ほかの関数を学ぶときも迷いにくくなります。

図でイメージすると理解しやすい

関数プロトタイプ宣言は、文章だけでも理解できますが、流れを図で見るとさらに分かりやすくなります。
たとえば、次のようなイメージです。

  • 先に関数の情報を宣言する
  • そのあと main 関数で呼び出す
  • 最後に関数本体で処理する

この順序が視覚的に見えると、「なぜ先に宣言が必要なのか」が自然につかめます。

この図では、上から順番にコンパイラが読んでいく様子を表しています。

最初に関数プロトタイプ宣言があるので、コンパイラは message_repeat 関数の存在を知ることができます。
そのため、次に main 関数の中で message_repeat(base_count) が現れても、「この関数は正しく呼び出されているかな」と確認できます。
そして最後に関数本体を読んで、実際の処理内容を理解します。

この流れを頭の中で持っておくと、関数を自分で作るときにもコードを整理しやすくなります。

関数プロトタイプ宣言がないとどうなるのか

学習の中では、「宣言が必要です」と覚えるだけで終わってしまいがちですが、なぜ必要なのかを理解することが大切です。

もし main 関数より前に関数の定義がなく、さらにプロトタイプ宣言もなければ、コンパイラは呼び出された関数の情報を事前に知ることができません。

すると、環境や書き方によってはエラーや警告の原因になります。
特に現代のC言語では、関数の情報を明確に書くことがとても重要です。

たとえば、次のようなミスを防ぎやすくなります。

  • 本当は int を渡すべきなのに、別の型の値を渡してしまう
  • 引数が1個の関数なのに、2個渡してしまう
  • int を返す関数だと思って使ったのに、実は void だった

こうした間違いを早い段階で見つけるためにも、関数プロトタイプ宣言はとても役立ちます。

呼び出す関数より上に定義があれば宣言は省略できるのか

これはよくある疑問です。
答えとしては、呼び出す前に関数定義そのものが書かれていれば、理屈の上ではプロトタイプ宣言なしでも動くことがあります

たとえば次のように、main より前に関数定義がある場合です。

ファイル名:13_4_2.c

#include <stdio.h>

// 表示回数を2倍にする関数
int message_repeat(int count)
{
    int result = count * 2;
    return result;
}

int main(void)
{
    int base_count = 3;
    int total_count = message_repeat(base_count);

    printf("あいさつは%d回表示します。\n", total_count);

    return 0;
}

この形なら、main が message_repeat を呼び出す時点で、コンパイラはすでにその関数定義を読んでいます。
そのため、別にプロトタイプ宣言を書かなくても理解できます。

ただし、実際の開発や学習では、

  • main を先に書いて読みやすくしたい
  • 関数をあとでまとめて定義したい
  • 複数ファイルで関数を共有したい

といった場面がよくあります。
そういうときに関数プロトタイプ宣言が活躍します。

ヘッダファイルとのつながり

関数プロトタイプ宣言を学ぶと、やがてヘッダファイルとの関係も見えてきます。
たとえば stdio.h には、printf などの関数のプロトタイプ宣言が書かれています。
だからこそ、私たちは printf を安心して使えるわけです。

つまり、自分で作る関数に対して書く関数プロトタイプ宣言は、標準ライブラリの関数を使うときに見えないところで行われている仕組みと同じ考え方です。

このことが分かると、関数プロトタイプ宣言は単なる文法ではなく、C言語のプログラム全体を支える大切な約束事だと分かってきます。

書くときに気をつけたいポイント

関数プロトタイプ宣言を書くときは、次の点に注意するとよいです。

注意点内容
セミコロンを付ける宣言の終わりには ; が必要
返却値型を正しく書くint や void などを間違えない
引数の型を正しく書く関数定義と一致させる
関数名を統一する宣言・呼び出し・定義で同じ名前にする
引数の数を一致させる宣言と定義で個数が違わないようにする

とくに初心者のうちは、宣言と定義の形をそろえることを意識するとミスが減ります。

たとえば、宣言が

int message_repeat(int count);

なのに、定義が

int message_repeat(double count)
{
    return count * 2;
}

のようになっていると、型が一致しません。
こうした不一致はバグや警告のもとになるので注意が必要です。

覚え方のコツ

関数プロトタイプ宣言は、次のように覚えると分かりやすいです。

宣言は、関数の予告
定義は、関数の本番

このイメージを持つと、宣言と定義の違いがかなり整理しやすくなります。

  • 宣言では「これからこんな関数が出てきます」と知らせる
  • 定義では「実際の処理はこうです」と中身を書く

という役割分担です。

また、見た目でも区別できます。

  • セミコロンで終わる → 宣言
  • 波かっこがある → 定義

この2点を押さえておくと、ソースコードを読んだときに迷いにくくなります。

学習の段階でおすすめの書き方

はじめのうちは、次の順番で書くと整理しやすいです。

  1. 必要な #include を書く
  2. 関数プロトタイプ宣言を書く
  3. main 関数を書く
  4. その下に関数本体を書く

この流れにしておくと、main 関数を先に読めるので、プログラム全体の目的がつかみやすくなります。
そのうえで、下にある関数本体で細かな処理を確認できるので、読み手にもやさしい構成になります。

今回のような小さなプログラムでも効果がありますし、これから関数が増えていくと、さらにこの書き方のありがたさを感じやすくなります。