記事には広告を含む場合があります。

C言語入門講座

【C/C++】関数の値渡し・ポインタ渡し(アドレス渡し)・参照渡し

2024年11月9日

おゆ

組み込み系プログラマとして5年働いていた元エンジニアです。得意言語はC言語とC++。本サイトでは学生および新人組み込み系プログラマに向けてプログラミング知識をわかりやすく解説しています。

C言語、C++の関数には値渡しとポインタ渡し(アドレス渡し)と参照渡しの3種類があります。

ここではその違いを解説します。

関数への値渡し

関数へ変数を渡すとき、特にアドレスやポインタを使わなければ値渡しになります。

例:

#include <stdio.h>
int plus1(int num)
{
    return num+=1;
}
void main()
{
    int num=0;
    printf("num=%d\n",plus1(num));
    printf("num=%d\n",num);
}

実行結果

num=1
num=0

plus1関数は引数numに1を足して返す関数です。

1つ目のprintfではplus1関数の戻り値を表示しています。

2つ目のprintfではnumの値をそのまま表示します。

plus1関数内でnumに1を足したのに何で2つ目のprintfの出力結果が0なの?
おゆ
実はplus1関数内のnumとmain関数内のnumは全く別の変数なんです。

main関数で作られたnumとplus1関数のnumはそれぞれメインメモリ内の別々の場所に保存されます。
値渡し

つまりこれらは異なる値を持つ別々の変数なのです。

おゆ
plus1関数のnumは関数を抜けると削除されます。

メインメモリとは何かについて

【初心者向け】コンピュータとプログラムの仕組みを図解でわかりやすく解説

コンピュータってどんな仕組みで動いてるの?CPUとかメモリとかって聞くけど、どんな働きをしてるの?わかりやすく教えてほしい。 プログラムってパソコン上でどうやって動いてるの?初心者でも理解できるように ...

続きを見る

メインメモリの使い方について

スタック領域・ヒープ領域・静的領域の仕組みと違いをわかりやすく解説

プログラムを実行するとメインメモリはどうやって使われるの? スタック領域・ヒープ領域・静的領域ってどんな仕組み?それぞれどう違うの? こんな疑問にお答えします。 おゆプログラマーにとってメインメモリの ...

続きを見る

ちまにみmain関数のnumのように呼び出し側の引数を実引数、plus1関数のnumのように定義側の引数を仮引数と呼びます。

おゆ
仮引数は実引数のコピーとして一時的に作られるという関係が名前からも見て取れます。

仮引数名は実引数名と異なっても同じ動きをします。

例2:

#include <stdio.h>
int plus1(int x) //仮引数名をxに変更
{
    return x+=1;
}
void main()
{
    int num=0;
    printf("num=%d\n",plus1(num));
    printf("num=%d\n",num);
}

実行結果

num=1
num=0

このように値渡しをすると関数内で実引数のコピー(仮引数)が作られます

仮引数は実引数とは全く別の変数なので、関数で仮引数を書き換えても実引数には影響しません

関数へのポインタ渡し(アドレス渡し)

値渡しでは実引数の値を関数内で書き換えることはできませんでした。

値を書き換えたい場合にはポインタ渡しが使えます。

おゆ
ポインタ渡しはアドレス渡しとも呼ばれます。

ポインタ渡しをする関数の定義では引数をポインタにします。

呼び出し時にはアドレスを渡します。

以下では値渡しの例2をポインタ渡しに変更してみました。

例:

#include <stdio.h>
int plus1(int *x)
{
    return *x+=1;
}
void main()
{
    int num=0;
    printf("num=%d\n",plus1(&num));
    printf("num=%d\n",num);
}

実行結果

num=1
num=1

plus1関数ではポインタxの値に1を足して戻り値としています。

そのため1つ目のprintfでは値渡しのときと同じように1が出力されます。

値渡しと異なるのは2つ目のprintfの出力です。

実引数としてアドレス&numを渡すと仮引数のポインタxにはnumのアドレスが入り、xはnumを示すポインタとなります。

アドレス・ポインタについて

【C言語】アドレスとポインタをわかりやすく解説!使うメリットは?

ポインタって何?使うメリットは?わかりやすく教えてほしい。 アドレスって何?ポインタに関係あるの?メモリのアドレスって言われてもピンとこない… こんなお悩みにお答えします。 アドレスとはメインメモリ上 ...

続きを見る

つまりポインタxの値*xはnumの値に等しくなります。
ポインタ渡し

よって*xに1を足すことはnumに1を足すことを意味します。

関数を抜けるとxは削除されますが、numは保持されます。

そのため2つ目のprintfの出力結果も1となります。

ポインタ渡しの注意点(NULLポインタ)

実はポインタ渡しのところで紹介したコード例は不完全です。

ポインタ渡しの関数には実引数としてNULLポインタが渡される可能性があるからです。

NULLポインタとはどこのアドレスも示していないポインタです。「int *p;」のように書くと適当な位置を示すポインタとなるため、このまま使うとエラーの原因になります。しばらく使わないポインタは「int *p=NULL;」と書き、明示的にNULLポインタであることを宣言しておきます。

関数呼び出し時にNULLポインタが渡されると実行結果に何も出力されずプログラムが終了してしまいます。

例1:

#include <stdio.h>
int plus1(int *x)
{
    return *x+=1;
}
void main()
{
    int num=0;
    int *p=NULL; //NULLポインタ
    printf("num=%d\n",plus1(p));
    printf("num=%d\n",num);
}

実行結果

(何も出力されない)

おゆ
今は原因がわかっているからまだいいですが、不意にこの状態になると原因を調べるのに時間が掛かります。余計な時間をかけないためにもNULLチェックは入れておきましょう。

しかし中には

NULLポインタなんて渡す予定ないんだけど…

と思う方もいらっしゃるかと思います。

おゆ
気持ちはわかりますがそれでも入れておくべきなんです。

プログラム開発は多くの場合、複数人で行います。

自分の知らないところで思いもよらない方法で関数を使われることはよくあります。

未来の自分がすっかり忘れてNULLポインタを渡してしまう可能性だってあります。

例1には以下のようにNULLチェックを入れておきましょう。

例2:

#include <stdio.h>
int plus1(int *x)
{
    if(x==NULL) //NULLチェック
    {
        return -1;
    }
    return *x+=1;
}
void main()
{
    int num=0;
    int *p; //NULLポインタ
    printf("num=%d\n",plus1(p));
    printf("num=%d\n",num);
}

実行結果

num=-1
num=0

NULLチェックに引っかかったら特定の値を返すようにしておきます。

例2では-1を返しています。

関数への参照渡し(C++限定)

参照渡しはC++でのみ使える渡し方です。

呼び出し時にはポインタ渡しと同じくアドレスを渡します。

ポインタ渡しのように関数内で実引数を変更できます。

以下の例はポインタ渡しの例を参照渡しに変更したものです。

例:

#include <stdio.h>
int plus1(int &x) //仮引数にはアドレスを定義
{
    return x+=1;
}
int main()
{
    int num=0;
    printf("num=%d\n",plus1(num)); //変数をそのまま渡す
    printf("num=%d\n",num);

    return 0;
}

実行結果

num=1
num=1

実引数には変数名を渡し、仮引数にはアドレスを定義します。

このときポインタ渡しと同様にnumの値とxの値は同じ場所に保存されます。

関数内でポインタを使わずに「x+=1」と記述すればxおよびnumに1が足されます。

実行結果もポインタ渡しと同じになります。

おゆ
やっていることはポインタ渡しと同じです。コードは参照渡しの方が見やすいのではないでしょうか。

値渡しとポインタ渡しのメリット・デメリット

値渡し

通常、関数を使う際には値渡しを使います。

コードが見やすくなりますし、変数管理も楽だからです。

値渡しのメリット

  • コードが理解しやすい
  • 思わぬタイミングで変数値が書き換えられることがない

ポインタ渡しや参照渡しを使う際には内部で値を書き換えているかを毎回確認する必要があります。

知らないうちに実引数が書き換えられると思わぬ動作の原因になるからです。

しかし値渡しではその心配はありません。

値渡しのデメリット

  • 実引数の値は書き換えられない
  • 戻り値は1つだけ(複数の仮引数を書き換えても関数外に値を渡せるのは1つ)
  • 大きなデータを実引数として渡すとメモリが大量に使われプログラムの動作が遅くなる

値渡しでは関数が呼び出されるたびに実引数を丸ごとコピーした仮引数が作られるので、その分のメインメモリの領域が占領されます。

変数にメモリ領域を割り当てて値を代入するのには多少なりとも時間が掛かるので、大きなデータを渡すとその分プログラム動作が遅くなります。

ポインタ渡し

以下のメリットを享受したい場合にはポインタ渡しを使います。

ポインタ渡しのメリット

  • 実引数の値を変更できる
  • 関数内で複数の実引数を書き換えられる
  • 大きなデータを渡してもプログラムの動作が早い

ポインタ渡しでは大きなデータを渡してもアドレス分のメモリ領域しか使われないのでプログラムの実行スピードは早いです。

ポインタ渡しのデメリット

  • コードが理解しにくくなる
  • NULLチェックが必要
  • 実引数が変更されるかを使うたびに確認する必要がある

ポインタ渡しは少々複雑なのでコードの理解が容易ではありません。

また、実引数が変更される可能性があるので、都度コードを確認しなくてはいけません。

ポインタ渡しは値渡しが不都合な場合にのみ使うのがおすすめです。

参照渡し

参照渡しはC++でしか使えませんが、コードが見やすい分ポインタ渡しより使いやすいのではと思います。

参照渡しのメリット

  • コードが理解しやすい
  • NULLチェックが不要
  • 実引数の値を変更できる
  • 関数内で複数の実引数を書き換えられる
  • プログラムの動作が早い

参照渡しのデメリット

  • C言語では使えない
  • 呼び出し側を見ても参照渡しか値渡しか見分けがつかない
  • 実引数が変更されるかを使うたびに確認する必要がある

参照渡しだと実引数に変数をそのまま渡すので、呼び出し側だけ見ると値渡しのように見えます。

「値渡しだと思っていたら中で実引数の値が書き換えられていた…」というミスに注意する必要があります。

C言語で行き詰まったら…

C言語・C++がわからない時に質問できるサイト・サービス5選

C言語を独学で勉強しているけど内容がイマイチわからない。もはや何がわからないのかもわからない。 C++のエラーが解決できない。ググってもわからない。わかる人に解説してほしい。 こんなお悩みにお答えしま ...

続きを見る

ブログランキング参加中。クリックしてもらえると励みになります。

ブログランキング・にほんブログ村へ

-C言語入門講座