【初心者向け】C言語の関数:引数と戻り値の使い方

C言語の関数は、入力(引数)を受け取り、処理した結果(戻り値)を呼び出し元へ返すのが基本です。

本記事では、初心者がつまずきやすい「引数と戻り値」の仕組みと使い方を、具体例とともに丁寧に解説します。

配列引数やポインタ、const の使い分け、複数の結果を返す設計、プロトタイプ宣言まで順に学びましょう。

引数と戻り値の意味

引数とは何かと関数シグネチャの読み方

シグネチャの基本要素

関数シグネチャ(関数の宣言・定義の見出し)は、戻り値の型・関数名・引数リストからなります。

たとえば次の宣言では、戻り値の型がint、関数名がadd、引数がint a, int bです。

C言語
// 戻り値:int, 関数名:add, 引数:int a と int b
int add(int a, int b);
  • 引数は「仮引数」と呼ばれ、呼び出し側で渡す値(実引数)を受け取ります。
  • 関数は「プロトタイプ宣言」と「定義」を区別します。プロトタイプ宣言は関数の存在と型をコンパイラに知らせ、定義は実際の処理本体です。

プロトタイプと定義の違い

C言語
// プロトタイプ宣言(ヘッダなどに書く)
double average(const int *arr, size_t n);

// 定義(ソースファイルに書く)
double average(const int *arr, size_t n) {
    // 本体
}

プロトタイプがあることで、コンパイラは引数や戻り値の型チェックを行えます。

C では関数オーバーロードはありません。

同名関数を型違いで複数定義することはできないため、シグネチャは一意である必要があります。

戻り値とは何かとreturn文の役割

return文の基本

returnは関数の実行を終了し、呼び出し元へ値を返します。

void関数ではreturn;(値なし)または省略も可能ですが、非void関数ではすべての経路で値を返す必要があります。

C言語
int max2(int a, int b) {
    if (a > b) return a;
    return b; // すべての経路で値を返す
}

注意点:未定義動作と寿命

  • void関数でreturn値を返さないと未定義動作になります。
  • ローカル変数のアドレスを返してはいけません(関数終了で寿命が切れるため)。
  • ポインタを返す設計では、指す先の寿命(静的領域、動的確保済みなど)を意識します。

C言語の引数の使い方と注意点

型と個数と順序の決め方

引数の型・個数・順序は関数の契約そのものです。

呼び出し側はこの契約に従って値を渡します。

順序を入れ替えるだけでロジックが破綻することがあります。

C にはオーバーロードがないため、意味が異なる操作は関数名で区別するか、引数の数や補助的なenumで切り替えます。

引数の順序は「主対象→条件や閾値→オプション」のように、自然に読める並びにするのが実務上のコツです。

値渡しの仕組みと呼び出し側が変わらない理由

Cの関数引数は「値渡し」です。

呼び出し側の値のコピーが関数に渡されるため、関数内で引数を変更しても呼び出し元の変数は変化しません。

例:インクリメント関数

C言語
#include <stdio.h>

// 値渡し(呼び出し側は変わらない)
void inc_wrong(int x) {
    x++; // xはコピーなので、呼び出し元の変数は変化しない
}

// アドレスを渡して変更(呼び出し側が変わる)
void inc_right(int *x) {
    (*x)++;
}

int main(void) {
    int a = 10;
    inc_wrong(a);
    printf("after inc_wrong: %d\n", a); // 10 のまま

    inc_right(&a);
    printf("after inc_right: %d\n", a); // 11 に更新

    return 0;
}
実行結果
after inc_wrong: 10
after inc_right: 11

配列引数の扱いとポインタに見える理由

Cでは、配列を関数に渡すと先頭要素へのポインタに自動変換(配列の先頭ポインタへの暗黙変換)されます。

したがって、int arr[]int *arrもシグネチャ上は同じ意味です。

関数内でsizeof(arr)としても配列のサイズではなくポインタのサイズになります。

C言語
#include <stdio.h>
#include <stddef.h>

size_t sum(const int arr[], size_t n) { // const int *arr と同等
    size_t s = 0;
    for (size_t i = 0; i < n; ++i) {
        s += arr[i];
    }
    printf("sizeof(arr) in function: %zu\n", sizeof(arr)); // ポインタのサイズ
    return s;
}

int main(void) {
    int a[5] = {1, 2, 3, 4, 5};
    printf("sizeof(a) in caller: %zu\n", sizeof(a)); // 配列の総バイト数
    size_t s = sum(a, 5);
    printf("sum: %zu\n", s);
    return 0;
}

実行結果例:64bit環境、int=4byte, ポインタ=8byte
sizeof(a) in caller: 20
sizeof(arr) in function: 8
sum: 15

配列全体の長さが必要なら、呼び出し側から要素数(n)を併せて渡してください。

constを使った引数の意図表現

読み取り専用の意図を明確にするため、const修飾子を使います。

特に配列・ポインタの引数では有効です。

  • const int *pint const *pも同じ意味): 参照先の整数は書き換え不可、ポインタ自体の差し替えは可
  • int * const p: 参照先の整数は書き換え可、ポインタ自体の差し替えは不可
  • const int * const p: 参照先もポインタ自体も書き換え不可

形式参照先の書き換えポインタの差し替え説明
int *p可能可能一般的な可変参照
const int *p不可可能読み取り専用参照
int * const p可能不可固定アドレスを扱うとき
const int * const p不可不可完全に不変の参照
※ポインタとconstの組み合わせ

読み取り専用の関数にconstを付けると、誤って書き換えるバグをコンパイル時に防ぎやすくなります。

C言語の戻り値の使い方

戻り値の型を選ぶ基準とvoid関数

戻り値の型は、返すべき情報の性質と範囲で選びます。

  • 計算結果の数値: オーバーフローや精度を考え、int/long long/doubleなど適切な型を選びます。
  • 要素数・サイズ: 非負であることが明確なのでsize_tが適しています。
  • 成功/失敗の有無: bool#include <stdbool.h>)やエラーコード(intenum)が実用的です。
  • 何も返す必要がない: void関数にします。

複数の情報を返すときの設計指針

Cは多値返却を直接サポートしないため、次のいずれかを選びます。

  1. 構造体を戻り値として返す(可読性が高い。小さな構造体なら効率も良い)
  2. ポインタ引数を使って出力を書き込む(複数値に向く。戻り値はステータス用に使える)
  3. グローバル変数は基本的に避ける(テストしづらくバグの温床)

一般に「戻り値はステータス(成功/失敗)、結果はポインタ引数」または「結果の構造体を戻す」設計が読みやすく安全です。

エラーコードやboolを戻り値に使う例

成功/失敗をboolで返し、結果はポインタ引数に書く例です。

C言語
#include <stdbool.h>
#include <errno.h>

// 成功: true (結果*outに格納)、失敗: false(ゼロ除算など)
bool safe_divide(int a, int b, int *out) {
    if (b == 0) {
        errno = EDOM; // ドメインエラー(任意)
        return false;
    }
    if (out) *out = a / b;
    return true;
}

このように戻り値を状態に割り当てると、失敗時の分岐が明確になります。

ポインタ引数で値を受け渡す方法

アドレスを渡して関数内で値を更新する

変数を更新したい場合はアドレス(ポインタ)を渡します。

C言語
#include <stdio.h>

void swap(int *a, int *b) {
    if (!a || !b) return; // 安全性のためのNULLチェック
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void) {
    int x = 3, y = 7;
    printf("before swap: x=%d, y=%d\n", x, y);
    swap(&x, &y);
    printf("after  swap: x=%d, y=%d\n", x, y);
    return 0;
}
実行結果
before swap: x=3, y=7
after  swap: x=7, y=3

複数の結果を返すためのポインタと構造体の比較

ポインタ出力引数の例

C言語
#include <stdbool.h>
#include <stddef.h>

// 成功時にmin_out, max_outへ書き込む。戻り値は成功/失敗
bool minmax2(const int *arr, size_t n, int *min_out, int *max_out) {
    if (!arr || n == 0 || !min_out || !max_out) return false;
    int mn = arr[0], mx = arr[0];
    for (size_t i = 1; i < n; ++i) {
        if (arr[i] < mn) mn = arr[i];
        if (arr[i] > mx) mx = arr[i];
    }
    *min_out = mn;
    *max_out = mx;
    return true;
}

構造体を戻す例

C言語
#include <stddef.h>

typedef struct {
    int min;
    int max;
    int ok; // 0:失敗, 1:成功(簡易フラグ)
} MinMax;

MinMax minmax(const int *arr, size_t n) {
    MinMax r = {0, 0, 0};
    if (!arr || n == 0) return r;
    r.min = r.max = arr[0];
    for (size_t i = 1; i < n; ++i) {
        if (arr[i] < r.min) r.min = arr[i];
        if (arr[i] > r.max) r.max = arr[i];
    }
    r.ok = 1;
    return r;
}
方法呼び出しの読みやすさ失敗時の扱い効率/ABI使いどころ
構造体を戻り値直感的(名前付きフィールド)okやタグで表現小さな構造体は最適化されやすい複数の関連値をひとまとめに返す
ポインタ出力引数+ステータス戻り値分かりやすい制御フロー(if)falseで早期return参照渡しでオーバーヘッド小成否判定+複数値を返したいとき

関数プロトタイプ宣言と型チェック

ヘッダに書くプロトタイプと引数・戻り値の宣言

実務では「ヘッダ(.h)にプロトタイプ」「実装(.c)に定義」「利用側(.c)で#include」の3分割が基本です。

C言語math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

#include <stddef.h>
#include <stdbool.h>

int add(int a, int b);
double average(const int *arr, size_t n);
bool safe_divide(int a, int b, int *out);

#endif // MATH_UTILS_H
C言語math_utils.c
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

double average(const int *arr, size_t n) {
    if (!arr || n == 0) return 0.0;
    long long sum = 0;
    for (size_t i = 0; i < n; ++i) sum += arr[i];
    return (double)sum / (double)n;
}

bool safe_divide(int a, int b, int *out) {
    if (b == 0 || !out) return false;
    *out = a / b;
    return true;
}
C言語main.c
#include <stdio.h>
#include "math_utils.h"

int main(void) {
    int s = add(12, 30);
    printf("add: %d\n", s);

    int v[] = {3, 4, 8, 10};
    printf("average: %.2f\n", average(v, 4));

    int q;
    if (safe_divide(42, 6, &q)) {
        printf("quotient: %d\n", q);
    } else {
        printf("divide error\n");
    }
    return 0;
}
実行結果
add: 42
average: 6.25
quotient: 7

型不一致のコンパイル警告と対処

プロトタイプがあれば、引数や戻り値の不一致を検出できます。

たとえば「意図せずdoubleintに渡している」などです。

C言語
#include <stdio.h>

int add(int a, int b);

int main(void) {
    double x = 1.5, y = 2.5;
    // 意図しない切捨て。-Wconversion などで警告が出ることがある
    int s = add(x, y); // 1 + 2 と解釈される可能性
    printf("%d\n", s);
    return 0;
}

実行結果コンパイル警告例(コンパイラやオプションにより異なります)
warning: implicit conversion from 'double' to 'int' changes value from 1.5 to 1 [-Wliteral-conversion]

対処としては、正しい型を渡す、明示的なキャストを見直す、関数の引数型を見直すなどがあります。

また、関数宣言は必ず先に見せること、int f(void)(引数なし)とint f()(引数が未指定:古い書き方で避ける)を混同しないことが重要です。

現代のCでは「引数なし」はint f(void)で表します。

初心者向けサンプルとよくあるミス

例 add関数と平均値関数での引数と戻り値の使い方

C言語
#include <stdio.h>
#include <stddef.h>

int add(int a, int b) {
    return a + b; // 戻り値で結果を返す
}

double average(const int *arr, size_t n) {
    if (!arr || n == 0) return 0.0; // エッジケースに注意
    long long sum = 0;
    for (size_t i = 0; i < n; ++i) sum += arr[i];
    return (double)sum / (double)n;
}

int main(void) {
    int s = add(7, 5);
    printf("add(7,5) = %d\n", s);

    int data[] = {1, 2, 3, 4};
    double avg = average(data, 4);
    printf("average = %.2f\n", avg);
    return 0;
}
実行結果
add(7,5) = 12
average = 2.50

商と余りを返す関数の設計例(余りはポインタ引数)

戻り値で商を返し、余りはポインタ引数に書き込む設計例です。

ゼロ除算に備えて成功可否をboolで知らせる案も合わせて示します。

C言語
#include <stdio.h>
#include <stdbool.h>

// 戻り値: 商, *rem に余り, *ok に成功可否(NULL可)
int div_with_remainder(int a, int b, int *rem, bool *ok) {
    if (b == 0) {
        if (rem) *rem = 0;
        if (ok) *ok = false;
        return 0; // 値は未定義扱いなので使わない
    }
    if (ok) *ok = true;
    int q = a / b;
    if (rem) *rem = a % b;
    return q;
}

int main(void) {
    int r;
    bool ok;
    int q = div_with_remainder(17, 5, &r, &ok);
    if (ok) {
        printf("17 / 5 -> quotient=%d, remainder=%d\n", q, r);
    }

    q = div_with_remainder(10, 0, &r, &ok);
    if (!ok) {
        printf("division by zero!\n");
    }
    return 0;
}
実行結果
17 / 5 -> quotient=3, remainder=2
division by zero!

この設計は、「主たる値(商)」を戻り値で返し、「付随情報(余り)」を出力引数で渡す良い例です。

よくあるエラー 引数の型ミスや戻り値の未使用

引数の型ミス

期待型と異なる実引数を渡すと、暗黙変換で意図しない値になったり、警告が出ます。

プロトタイプを正しく書き、-Wall -Wextra -Wconversionなどの警告オプションを活用しましょう。

int f() vs int f(void)

後者が「引数なし」の正しい宣言です。

前者は古い書き方で、未指定引数を意味し紛らわしいため避けます。

戻り値の未使用

scanffgetsなどの戻り値を無視すると、失敗を見落とします。

戻り値で状態を返す関数は、必ずチェックする習慣をつけてください。

void関数でreturnしない

すべての経路で値を返すようにしましょう。

コンパイラの警告を必ず修正します。

ローカルのアドレスを返す

関数終了後に無効になるため、決して返さないでください。

必要なら動的確保(malloc)や静的領域、呼び出し側で用意したバッファを使います。

まとめ

C言語の関数設計では、引数と戻り値が「契約」です。

引数は値渡しである点を踏まえ、呼び出し側に影響を及ぼす必要がある場合はポインタを使います。

配列引数はポインタに見える(先頭要素へのポインタに変換される)ため、要素数は別途渡すのが基本です。

読み取り専用はconstで意図を明確化し、戻り値は「主結果」か「ステータス」に役割分担すると可読性が上がります。

複数の結果は出力ポインタか構造体で返し、プロトタイプをヘッダで宣言して型チェックを有効にしましょう。

コンパイラ警告は味方です。

常にチェックし、ゼロ除算や未定義動作を避ける堅牢なコードを心がけてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!