
C言語のきほん|値渡しの基本
値渡しのしくみがわかると、関数の中と外の関係がすっきり見えてくる
C言語で関数を学んでいると、引数に数字を渡したり、変数の値を渡したりする場面がたくさん出てきます。
そのとき、「関数に値を渡したあと、元の変数はどうなるのだろう?」と気になることがあります。
たとえば、main 関数にある変数の値を別の関数へ渡したときに、関数の中でその値を変更したら、元の変数まで変わってしまうのでしょうか。
この点を正しく理解するために大切なのが、値渡しの基本です。
C言語では、関数呼び出しのときに、実引数の値がそのまま仮引数へ渡されるのではなく、値がコピーされて渡されるという考え方をします。
このしくみが分かると、
- 実引数と仮引数の関係
- なぜ関数の中で値を変えても元の変数が変わらないのか
- 値を渡す場合とアドレスを渡す場合の違い
といったポイントがとても見えやすくなります。
関数を正しく使うための土台になる大切な内容なので、ここでしっかり整理していきましょう。
値渡しとは
C言語では、関数を呼び出すときに実引数の値がコピーされて、仮引数に代入されます。
これを一般に値渡しと呼びます。
たとえば、main 関数にある変数 a の値が 10 だったとします。
その a を関数 func1 に渡すと、func1 側では仮引数 n に 10 が入ります。
ここで大事なのは、a そのものが移動するわけではないという点です。
渡されるのは a という変数そのものではなく、a に入っている値です。
つまり、イメージとしては次のようになります。
- main 関数の a に 10 が入っている
- 関数呼び出しで 10 がコピーされる
- func1 関数の n に 10 が入る
このとき、a と n は同じ値を持っていますが、別々の変数です。
実引数と仮引数を整理しよう
値渡しを理解するには、まず実引数と仮引数の違いをはっきりさせることが大切です。
| 用語 | 書かれる場所 | 例 | 意味 |
|---|---|---|---|
| 実引数 | 関数を呼び出す側 | func1(a); | 実際に渡す値や変数 |
| 仮引数 | 関数を定義する側 | void func1(int n) | 受け取るための変数 |
たとえば、
func1(a);の a は実引数です。
一方で、
void func1(int n)の n は仮引数です。
このとき、実引数 a の値がコピーされて、仮引数 n に入ります。
この「コピーされる」という感覚がとても大切です。
サンプルプログラム
ここでは、親しみやすい例として、整数値を受け取って表示する関数を作成します。
ファイル名:13_10_1.c
// 値渡しの基本を確認するプログラム
#include <stdio.h>
void show_number(int value);
int main(void)
{
int score = 80;
show_number(score);
printf("main関数のscore=%d\n", score);
return 0;
}
// 渡された値を表示する関数
void show_number(int value)
{
printf("show_number関数のvalue=%d\n", value);
}実行結果例
show_number関数のvalue=80
main関数のscore=80このプログラムでは、main 関数の変数 score の値 80 を show_number 関数へ渡しています。
その結果、仮引数 value に 80 がコピーされて表示されます。
このプログラムの見方
このプログラムで注目したいのは、次の2行です。
int score = 80;
show_number(score);ここでは、変数 score を渡しています。
でも、show_number 関数に渡されているのは、score という変数そのものではありません。
score の中に入っている 80 という値です。
そして関数定義側では、
void show_number(int value)となっているので、value がその値を受け取ります。
流れを表で整理すると、こうなります。
| 順番 | 内容 |
|---|---|
| 1 | main 関数で score に 80 を代入する |
| 2 | show_number(score); を実行する |
| 3 | score の値 80 がコピーされる |
| 4 | 仮引数 value に 80 が代入される |
| 5 | 関数内で value を表示する |
| 6 | 関数が終わり、main 関数へ戻る |
値がコピーされるとはどういうことか
値渡しで一番大切なのは、実引数と仮引数は別々の場所に存在するということです。
同じ 80 という値を持っていても、
- main 関数の score
- show_number 関数の value
は、メモリ上では別の変数です。
このことを表で整理すると分かりやすいです。
| 項目 | main関数側 | show_number関数側 |
|---|---|---|
| 変数名 | score | value |
| 値 | 80 | 80 |
| 実体 | 別の変数 | 別の変数 |
見た目には同じ 80 ですが、片方を変えても、もう片方は自動では変わりません。
なぜなら、同じ箱を共有しているのではなく、別々の箱に同じ値が入っている状態だからです。
関数の中で値を変えても元の変数は変わらない
値渡しの特徴がいちばんよく分かるのは、関数の中で仮引数の値を変えたときです。
次の例を見てみましょう。
ファイル名:13_10_2.c
// 値を変更しても元の変数は変わらないことを確認するプログラム
#include <stdio.h>
void change_value(int number);
int main(void)
{
int points = 50;
printf("呼び出し前のpoints=%d\n", points);
change_value(points);
printf("呼び出し後のpoints=%d\n", points);
return 0;
}
// 受け取った値を変更する関数
void change_value(int number)
{
number = 100;
printf("change_value関数のnumber=%d\n", number);
}実行結果例
呼び出し前のpoints=50
change_value関数のnumber=100
呼び出し後のpoints=50ここでは、main 関数の points は 50 です。
それを change_value に渡しています。
関数の中では、
number = 100;として値を変更していますが、main 関数に戻ったあとの points は 50 のままです。
これは、number が points そのものではなく、points の値をコピーして作られた別の変数だからです。
なぜ安全な方法といえるのか
値渡しは、元の変数が勝手に変更されないという意味で、安全な方法といえます。
たとえば、main 関数で大事な変数を使っていたとして、その値を別の関数へ渡すたびに元の値まで変わってしまうと、プログラム全体がとても分かりにくくなります。
でも値渡しなら、関数側ではコピーされた値を扱うだけです。
そのため、呼び出し元の変数をうっかり壊してしまう心配が減ります。
| 値渡しの特徴 | 内容 |
|---|---|
| 元の変数が守られる | 仮引数を変えても実引数は変わらない |
| 関数の影響範囲が分かりやすい | 関数内の変更が外に漏れにくい |
| 初学者にも理解しやすい | 渡したのは値そのものだと考えやすい |
この考え方は、関数を安心して使ううえでとても大切です。
図で見るとさらに理解しやすい
値渡しは、メモリ上で別々の変数になっていることを図で見ると、とても分かりやすくなります。

この図では、main 関数の score に入っている 80 が、そのまま右側の value に移動しているのではなく、コピーされていることを表しています。
score と value は別々の変数ボックスとして描かれているので、同じ 80 を持っていても別の存在だと分かります。
ここが、値渡しの基本イメージです。
定数を渡す場合も値渡し
これまでの説明では変数を例にしてきましたが、実引数には定数を書くこともできます。
この場合も考え方は同じです。
たとえば、
show_number(25);と書いた場合、25 という値が仮引数へ入ります。
void show_number(int value)
{
printf("%d\n", value);
}このときも、仮引数 value には 25 が入ります。
つまり、実引数が変数でも定数でも、関数が受け取るのは値です。
式の結果を渡すこともできる
実引数には変数や定数だけでなく、式を書くこともできます。
たとえば、
show_number(10 + 5);のような形です。
この場合、10 + 5 が先に計算され、その結果である 15 が仮引数へ渡されます。
つまり、これもやはり「値がコピーされる」という考え方です。
| 実引数の書き方 | 仮引数に渡るもの |
|---|---|
| show_number(25); | 25 |
| show_number(score); | score の値 |
| show_number(10 + 5); | 15 |
このように見ると、関数に渡されるのは、最終的に求められた値だと分かります。
値渡しとアドレス渡しの違いに少し触れておこう
今回の中心は値渡しですが、文中にもあるように、C言語では変数や配列のアドレスを渡すこともできます。
その場合、関数側ではポインタで受け取ることになります。
値渡しとアドレスを渡す場合の違いをざっくり整理すると、次のようになります。
| 方法 | 渡すもの | 関数内で元の変数を変更できるか |
|---|---|---|
| 値渡し | 値そのもの | 基本的にできない |
| アドレスを渡す | 変数の場所 | できる |
今の段階では、まず
値渡しでは元の変数は変わらない
ということをしっかり押さえておけば十分です。
あとでポインタを学ぶと、「なぜアドレスを渡すと元の変数を変更できるのか」がつながって理解しやすくなります。
関数宣言・呼び出し・定義を対応させて見る
値渡しを理解するときも、関数宣言・呼び出し・定義を対応させて見ると分かりやすいです。
たとえば、次の形です。
| 役割 | コード | 意味 |
|---|---|---|
| 関数プロトタイプ宣言 | void show_number(int value); | int 型の値を1つ受け取る |
| 関数呼び出し | show_number(score); | score の値を渡す |
| 関数定義 | void show_number(int value) | value で受け取って使う |
この3つがつながっていることを意識すると、値がどう流れているかが見やすくなります。
よくある勘違い
値渡しでは、最初のうちにいくつか勘違いしやすいポイントがあります。
| よくある勘違い | 正しい考え方 |
|---|---|
| 実引数の変数そのものが関数へ渡る | 渡るのは変数に入っている値 |
| 仮引数を変えたら実引数も変わる | 値渡しでは変わらない |
| 同じ値なら同じ変数だと思う | 同じ値でも別々の変数 |
| 関数に渡したら元の変数が使えなくなる | 実引数はそのまま残る |
このあたりを意識しておくと、関数の動きがかなり理解しやすくなります。
実践的な見方
値渡しは文法として覚えるだけでなく、プログラムを読むときの見方としても大切です。
関数呼び出しを見たら、次のように考えると整理しやすいです。
- 実引数には何が書かれているか
- その値はいくつになるか
- 仮引数は何という名前で受け取るか
- 仮引数を変えても実引数には影響するか
この順番で追うと、関数の中と外の関係が見えやすくなります。
練習用のシンプルな確認例
最後に、値渡しの基本をもう1回確認しやすい短い例を載せておきます。
ファイル名:13_10_3.c
// 値渡しでは元の変数が変わらないことを確認するプログラム
#include <stdio.h>
void add_ten(int number);
int main(void)
{
int value = 30;
printf("呼び出し前のvalue=%d\n", value);
add_ten(value);
printf("呼び出し後のvalue=%d\n", value);
return 0;
}
// 受け取った値に10を足して表示する関数
void add_ten(int number)
{
number = number + 10;
printf("add_ten関数のnumber=%d\n", number);
}実行結果例
呼び出し前のvalue=30
add_ten関数のnumber=40
呼び出し後のvalue=30この結果を見ると、関数の中では 40 になっていても、main 関数の value は 30 のままだと分かります。
これが、値渡しの基本そのものです。
学習のコツ
値渡しをしっかり理解したいときは、同じ値でも別の変数という考え方を何度も意識するのがコツです。
とくに、
- 実引数の値がコピーされる。
- 仮引数は別の変数である。
- 仮引数を変えても実引数は変わらない。
この3点をセットで覚えておくと、今後ポインタやアドレス渡しを学ぶときにも、とても役立ちます。
