本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。
第33回|C言語でなぜ配列を関数に渡すと姿が変わるのか:データと関数をつなぐ仕組み

はじめてのC言語 | 第33回
同じ景色から見る、もうひとつの不思議
UNIX Cafe のカウンターで、
ミナちゃんがノートを開きながら、少し首をかしげています。
ミナちゃん先生……配列を関数に渡したら、なんだか挙動がおかしいんです。
C言語を学ぶ多くの人が、ここで一度、同じ壁にぶつかります。
- 関数の中で配列の値を書き換えたら、呼び出し元の配列まで変わってしまった。
- sizeofでサイズを調べたら、期待していた大きさにならなかった。
「ちゃんと配列を渡したはずなのに……?」
今日はその違和感の正体を、
これまでに学んできた「配列」と「ポインタ」の関係から、もう一度見つめ直してみましょう。
まずは復習|関数に「値」を渡すとどうなる?
ではまず、比較のために、普通の変数の場合がどうだったか思い出してみましょう。
int型のような普通の変数を関数に渡した場合、
- 関数の中でその値を使うことができる
- しかし、中で値を変更しても、呼び出し元の変数は変わらない
というルールがありましたね。
これは、関数に渡されているのが変数そのものではなく、「値のコピー」だからです。
UNIX Cafeの例えで言うなら、これは調理場に「材料そのもの」を渡すのではなく、
単に「分量を書いたメモ」を渡しているような状態です。
メモの数字をいくら書き換えても、元の材料が変わることはありませんよね。
では、配列を関数に渡すと何が起きるのか
さて、いよいよ本題の配列です。
関数を定義するとき、引数に
int a[]と書かれているのを見ると、多くの人が直感的にこう考えてしまいます。
「なるほど、配列がまるごと渡されるんだな」と。
でも、ここに C言語らしい落とし穴 があります。
関数の中で受け取っているのは、配列全体ではない
ここで、これまで何度も確認してきた、配列に関する大切なルールを思い出してみましょう。
- 配列は、メモリ上に「ずらりと並んだ箱」のようなもの
- 多くの式では、配列名は先頭要素へのポインタとして扱われる
でしたね。
このルールが分かれば、もう答えは見えたも同然です。
関数に配列を渡したとき、実際に関数へ渡されるのは、
- 配列そのものではなく
- その配列の先頭要素がどこにあるかという「場所の情報」
なのです。
「配列がポインタになる」という表現は、少し誤解を招く
C言語を学んでいると、こんな説明をよく耳にします。
「配列は、関数に渡すとポインタに変わるんだ」と。
この説明は、関数の引数に限って見ると大きく外れてはいません。
ただし、配列そのものが別のものに変身するわけではありません。
正確には、関数の仮引数に int a[] と書いても、
その仮引数は int *a として扱われます。
そして、呼び出し側で配列名を渡すと、
その配列の先頭要素へのポインタが関数に渡されます。
つまり、配列全体がコピーされるのではなく、
「この場所から配列が始まっています」という情報が渡されるのです。
バックヤードの作業で例えるなら、棚にある商品そのものをカウンターに運んでくるのではなく、
その商品が置いてある棚の「ロケーション札」を渡している状態です。
だから、関数の中で書き換えると、外の配列も変わる
これで、ミナちゃんが最初に抱いた疑問が解けますね。
関数の中で配列の中身を書き換えると、呼び出し元の配列まで変わってしまう。
この現象は、魔法でもなければ、もちろん事故でもありません。
理由はとてもシンプルです。
関数の中と外で、同じ配列の実体を見ているからです。
関数に渡されるポインタ値はコピーです。
けれど、そのコピーされたポインタも、呼び出し元と同じ配列を指しています。
ですから、関数の中で配列の要素を一つ書き換えれば、
外から同じ配列を見たときにも、その結果が反映されています。
考えてみれば、とても素直で当たり前の話だったのです。
sizeof が配列全体の大きさにならない理由
C言語の配列でよくある、もう一つの疑問。
「関数の中で sizeof を使うと、なぜ配列全体のサイズが分からないのか」
これも、理由は同じです。
関数が受け取っているのは、箱がずらりと並んだ配列全体ではなく、あくまで「先頭要素の場所」という情報です。
一枚の「ロケーション札」だけを渡されても、その棚にいくつの箱が並んでいるかは分かりません。
どこまでがその配列の範囲なのかは、場所の情報だけでは判断できないのです。
そのため、関数の中で sizeof(a) を実行しても、「配列全体の大きさ」は計算できません。sizeof が教えてくれるのは、仮引数 a の型であるポインタの大きさです。
ミナちゃんの中で、点と点が線でつながった瞬間
そのとき、ミナちゃんはふっと顔を上げました。
その瞳には、今までの戸惑いではなく、確かな納得の色が浮かんでいます。
今までバラバラだと思っていた「配列」、「ポインタ」、そして「関数」。
それらが、本当はすべて同じ一つのルールで動いていたことに気づいたのです。
「そっか……。配列そのものを丸ごと渡しているつもりだったけど、関数に渡っていたのは、配列の先頭を示す“場所”だったんだ」
そのたった一つの気づきが、これまでモヤモヤしていた数々の不思議な現象を、一本のきれいな線で結びつけてくれたのです。
まとめ:関数に配列を渡すときに、本当に起きていること
C言語の配列と関数が織りなす、少し不思議な物語の核心は、とてもシンプルでした。
- 配列名は、多くの式で先頭要素へのポインタとして扱われること。
- 関数の仮引数に
int a[]と書いても、実際にはint *aとして扱われること。 - だから、関数に配列を渡したときに実際に送られるのは、配列全体ではなく先頭要素へのポインタであること。
関数の中では sizeof(a) が配列全体ではなくポインタの大きさを返すため、
まるで配列が別のものに「変身」したかのように見えていたかもしれません。
しかし、配列が壊れたわけではありません。
関数は、呼び出し元にある同じ配列を、ポインタを通して見ているだけなのです。
関数に何か特別な魔法があるわけではありません。
ただ、私たちは「配列全体」ではなく「場所」を渡していた。
すべての現象は、このたった一つの、とても素直なルールから生まれていたのです。
次へ進む前に:残された、たった一つの疑問
ここまでの話で、配列を渡す本当の意味が分かりました。
そうすると、今度はこんな疑問が浮かんできませんか?
「配列の“場所”しか渡せないなら、肝心の『箱の数』は、どうやって関数に伝えればいいんだろう?」と。
次回は、配列という「場所の情報」と一緒に、もう一つ、何を渡すべきなのか。
その答えを、これまでと同じ景色の中から一緒に見つけていきましょう。
安心してください。もう新しく覚えることは、ほとんどありません。
これまで見てきた景色の中に、そのヒントは隠されていますから。
もう一度、はじめからゆっくりと
ここまで読み進めてきた今、もう一度最初の回に戻ってみてください。
はじめは少し難しく感じた内容も、
きっと違った景色に見えてくるはずです。
C言語は、一度で理解するものではなく、
何度か行き来しながら、少しずつ深まっていく言語です。
最初の一歩に戻ることは、後退ではありません。
それは、理解を確かなものにするための大切な一歩です。
👉 もう一度、第1回から読み直してみましょう。
復習してみよう
復習したい方は、こちらもあわせてご覧ください。














