このトピックは、第2部のポインタへ続く最重要の伏線です。ここを曖昧にしたままポインタに進むと、必ず迷子になります。
Cの関数に値を渡すと、渡るのは原本ではなくコピーです。書類の原本を手渡すのではなく、コピー機で複製を取って渡すイメージ。これを値渡しと呼びます。
#include <stdio.h>
void add_ten(int n)
{
n = n + 10; // 手元のコピーを書き換えただけ
printf("%d\n", n); // 15
}
int main(void)
{
int n = 5;
add_ten(n);
printf("%d\n", n); // 5 のまま!
return 0;
}add_ten の中の n と main の n は、名前が同じでも別の箱です。前のトピックのコールスタックで言えば、add_ten のフレームに新しく作られた n に、main の n の値 5 が写し取られただけ。フレームが降ろされれば、書き換えた 15 ごと消えてしまいます。
int も double も char も、引数は例外なくコピーされます。第3部で学ぶ構造体のような大きなデータでも、まるごと写しが取られます。渡す=写す。これがCの基本ルールです。
有名なのが「入れ替え関数が動かない」問題です。
#include <stdio.h>
void swap(int a, int b) // これは動かない
{
int t = a;
a = b;
b = t;
}
int main(void)
{
int x = 3, y = 7;
swap(x, y);
printf("%d %d\n", x, y); // 3 7(入れ替わらない)
return 0;
}コピーどうしをどれだけ丁寧に入れ替えても、原本は無傷です。
では、どうすれば原本を変えられるのか
方法は2つあります。ひとつは、結果を戻り値で返して呼び出し元が代入し直すこと。もうひとつが、原本の置き場所(番地)を渡すこと。後者がポインタで、第2部の主役になります。
int add_ten(int n) // 値を返す形なら意図どおり動く
{
return n + 10;
}
// 呼び出し側: n = add_ten(n);関数から結果を持ち帰る3つの手
- 戻り値で返す(いまできる方法。いちばん安全で分かりやすい)
- 原本の番地を渡して直接書き換えてもらう(第2部・ポインタ)
- グローバル変数を共有する(追跡しづらいので最後の手段)
初学のうちは1番だけで十分です。「戻り値は1つしか返せないのに、3つの値を更新したい」——そんな場面に出会ったとき、2番が必要になる理由が実感として分かります。
罠・注意
- 「関数に渡したのに変わらない」は初学者の誤解の第1位です。原因はほぼ必ず値渡しです。仮引数を関数の中で書き換えること自体は合法なので、コンパイラは警告すら出しません。動かして初めて気づくタイプの間違いです。
- ただし配列だけは様子が違って見えます。関数に渡した配列は中身が書き換わります(t08-07で扱います)。これは例外ではなく、渡っているものが値ではないためです。
- コピーには費用もあります。大きなデータを毎回コピーするのは無駄で、これもポインタを使う理由になります。
ここまでの話をひとことにすると、「関数の中と外は、値でしかつながっていない」ということです。第2部でポインタを学ぶと、このつながり方に新しい選択肢が加わります。そのとき「なぜわざわざ番地を渡すのか」で悩まないために、いまは値渡しの動きだけをしっかり身体に入れておいてください。
まとめ: 引数はコピー。関数の中の変更は呼び出し元に届きません。届けたいなら「返す」か「番地を渡す」。この一文が、ポインタ理解の土台になります。