C言語基礎|関数原型宣言と関数定義

コンパイラにも“事前に自己紹介”が必要

C言語のコンパイラは、私たちが本を読むみたいに 上から下へ 順番にコードを読んでいきます。
だから、main の途中でいきなり未知の関数が出てくると、

「え、これ何? 引数いくつ? 戻り値の型は?」

ってなって困ります。
その“困らないための自己紹介”が 関数原型宣言(プロトタイプ宣言) です。

そして、実際に処理の中身(本体)を書いて関数を完成させるのが 関数定義
この2つの役割をきちんと分けて理解すると、プログラムが一気に読みやすく、直しやすくなります。

サンプルプログラム

2つの整数を読んで、差の絶対値を表示するプログラムを例に解説をします。

プロジェクト名:chap6-12-1 ソースファイル名:chap6-12-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

// 関数原型宣言(プロトタイプ)
int abs_diff(int x, int y);

int main(void)
{
    int a, b;

    puts("2つの整数を入力してください。");
    printf("1つ目:");
    scanf("%d", &a);
    printf("2つ目:");
    scanf("%d", &b);

    printf("差の大きさは%dです。\n", abs_diff(a, b));
    return 0;
}

// 関数定義(中身)
int abs_diff(int x, int y)
{
    if (x > y)
        return x - y;
    else
        return y - x;
}

実行例

2つの整数を入力してください。
1つ目:20
2つ目:55
差の大きさは35です。

このプログラムでは、main が abs_diff を呼び出す前に、上で関数原型宣言があるので、コンパイラは安心して読み進められます。

まず整理:宣言と定義は何が違うの?

関数原型宣言と関数定義の違い

項目何を書く?目的末尾のセミコロン実体(中身)は作る?
関数原型宣言返却値型 / 関数名 / 仮引数の型仕様を先に伝える(呼び出し可能にする)必要作らない
関数定義原型宣言の内容 + { } の中身実際の処理を書く(関数を完成させる)不要作る

この表は「原型宣言=仕様だけ」「定義=仕様+中身」を一発で見分けられるようにするための整理図です。

コンパイラが読む順番と“事前情報”

上から読むコンパイラとプロトタイプ

コードの並びコンパイラの気持ち
int abs_diff(int x, int y);OK、あとでこの関数が出てくるんだな(型も把握)
main の中で abs_diff(a, b)OK、呼び出し方合ってるかチェックできる
int abs_diff(int x, int y) { ... }OK、中身ここね

この図のポイントは「呼び出し位置より前に、最低限の仕様が必要」ということです。

関数原型宣言の書式と役割

書式

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

例:

  • int abs_diff(int x, int y);

何をする“命令”なの?

関数原型宣言は、次の情報を コンパイラ人間(読み手) に伝えます。

  • 返却値は int
  • 引数は int を2つ受け取る
  • 関数名は abs_diff

これがあるおかげで、呼び出し側で 間違った呼び方 をすると検出しやすくなります。

関数定義の書式と役割

書式

  • 返却値型 関数名(仮引数宣言) { 文… }

例:

  • int abs_diff(int x, int y) { ... }

何をする“命令”なの?

関数定義は、関数という部品を完成させるものです。
原型宣言が「仕様書」なら、定義は「完成品+中身」です。

どこに置くのが読みやすい?おすすめ配置パターン

関数の配置には大きく2つの流派があります。

配置パターンの比較

パターン並び関数原型宣言長所短所
A:定義を先に置く使われる関数 → mainなくても動く原型宣言が不要でシンプルmain が下に沈みやすい
B:原型→main→定義原型宣言 → main → 関数定義必要main を先に読めて全体像が早い仕様変更時に原型と定義の両方修正が必要

教材やチーム開発では、main を早く見せたいことが多いので B がよく使われます。
一方で、小さめのプログラムでは A もスッキリしていてアリです。

“仕様変更”の注意点:原型と定義がズレると危ない

たとえば abs_diff を

  • long を返すようにした
  • 引数を3つに増やした

みたいに変更したとき、原型宣言と関数定義の両方を直さないと、混乱の元になります。

ありがちなトラブル例(イメージ)

  • 原型:int abs_diff(int, int);
  • 定義:int abs_diff(int, int, int) { ... }

こういうズレがあると、コンパイルエラーや警告の原因になります。
なので「原型と定義は必ず同じ仕様にする」って覚えておくと安全です。

重要ポイント(短くまとめ)

  • コンパイラは上から読むので、呼び出しより前に関数の仕様が必要
  • 関数原型宣言は仕様を伝えるだけ(中身は作らない)
  • 関数定義は中身を作って関数を完成させる。
  • 置き方は2パターンあるが、読みやすさ重視なら 原型→main→定義 が便利なことが多い。