C言語で関数を呼び出す際、引数として渡した変数の値が、関数の中で変更されても、呼び出し元の変数には影響がない場合があります。一方で、関数内での変更が、呼び出し元の変数に直接反映されることもあります。この違いを生むのが、C言語における引数の渡し方である**「値渡し」と「アドレス渡し」**です。
この2つの違いを理解することは、C言語をマスターする上で非常に重要です。今回は、それぞれの仕組みと使い方を、分かりやすい例と共に徹底解説します。
値渡し:引数の値をコピーして渡す方法
値渡しは、C言語におけるデフォルトの引数の渡し方です。関数を呼び出す際に、引数として指定した変数の値そのものがコピーされ、関数の仮引数に渡されます。
** analogy:** 値渡しは、**「書類のコピーを渡す」**ようなものです。渡された側(関数)がコピーに何か書き込んでも、元の書類(呼び出し元の変数)には何の影響もありません。
値渡しのサンプルコード
次のコードは、main
関数からadd_and_print
関数に変数を渡し、関数内で値を変更しても、main
関数の元の変数が変わらないことを示しています。
#include <stdio.h>
// 2つの数値を受け取り、それぞれをインクリメントして合計を返す関数
int add_and_print(int val1, int val2);
int main(void) {
int a = 10;
int b = 20;
int result;
printf("【呼び出し前】main -> a: %d, b: %d\n", a, b);
// aとbの「値」がadd_and_print関数にコピーされる
result = add_and_print(a, b);
printf("【呼び出し後】main -> a: %d, b: %d\n", a, b); // aとbの値は変わらない!
printf("計算結果: %d\n", result);
return 0;
}
int add_and_print(int val1, int val2) {
printf(" 【関数内・処理前】val1: %d, val2: %d\n", val1, val2);
// コピーされた値(val1, val2)を変更する
val1++;
val2++;
printf(" 【関数内・処理後】val1: %d, val2: %d\n", val1, val2);
return val1 + val2;
}
実行結果:
【呼び出し前】main -> a: 10, b: 20
【関数内・処理前】val1: 10, val2: 20
【関数内・処理後】val1: 11, val2: 21
【呼び出し後】main -> a: 10, b: 20
計算結果: 32
add_and_print
関数内でval1
とval2
の値を変更しても、main
関数のa
とb
の値は全く変わっていないことが分かります。これが値渡しの特徴です。
アドレス渡し:引数のメモリアドレスを渡す方法
アドレス渡しは、値のコピーではなく、変数が格納されているメモリアドレスを関数に渡す方法です。これにより、関数は呼び出し元の変数を直接操作できるようになります。アドレスを扱うために、ポインタを利用します。
** analogy:** アドレス渡しは、**「共有フォルダにある書類のリンク(アドレス)を渡す」**ようなものです。渡された側(関数)がそのリンクから書類を編集すると、元の書類そのものが変更されます。
アドレス渡しのサンプルコード
main
関数の変数を、関数内で直接変更するプログラムを見てみましょう。
- 関数を呼び出す側:
&
を付けて変数のアドレスを渡す。 - 関数を定義する側:
*
を付けて引数をポインタとして受け取る。 - 関数内で値にアクセスする際:
*
を付けてポインタが指す先の値を操作する。
#include <stdio.h>
// 2つの数値のアドレスを受け取り、値を直接変更して合計を返す関数
int add_and_modify(int *ptr1, int *ptr2);
int main(void) {
int a = 10;
int b = 20;
int result;
printf("【呼び出し前】main -> a: %d, b: %d\n", a, b);
// aとbの「アドレス」をadd_and_modify関数に渡す
result = add_and_modify(&a, &b);
printf("【呼び出し後】main -> a: %d, b: %d\n", a, b); // aとbの値が変わっている!
printf("計算結果: %d\n", result);
return 0;
}
int add_and_modify(int *ptr1, int *ptr2) {
printf(" 【関数内・処理前】*ptr1: %d, *ptr2: %d\n", *ptr1, *ptr2);
// ポインタが指す先の値(つまりmainのaとb)を直接変更する
(*ptr1)++;
(*ptr2)++;
printf(" 【関数内・処理後】*ptr1: %d, *ptr2: %d\n", *ptr1, *ptr2);
return *ptr1 + *ptr2;
}
実行結果:
【呼び出し前】main -> a: 10, b: 20
【関数内・処理前】*ptr1: 10, *ptr2: 20
【関数内・処理後】*ptr1: 11, *ptr2: 21
【呼び出し後】main -> a: 11, b: 21
計算結果: 32
今度は、add_and_modify
関数内での変更が、main
関数のa
とb
に直接反映されていることが明確に分かります。
まとめ:値渡しとアドレス渡しの使い分け
値渡し | アドレス渡し | |
渡すもの | 値のコピー | 値のメモリアドレス |
元の変数 | 影響を受けない(安全) | 影響を受ける(変更可能) |
主な用途 | ・関数が値を参照するだけでよい場合<br>・計算結果を戻り値で返す場合 | ・関数内で複数の値を変更したい場合<br>・大きなデータ(構造体など)のコピーを防ぎたい場合 |
基本的な考え方:
- 安全な「値渡し」を基本とし、関数内で呼び出し元の変数を変更する必要がある場合にのみ、「アドレス渡し」を使う。
この2つの違いを正しく理解し、適切に使い分けることが、C言語プログラミング上達の鍵となります。