C言語講座 第7回

関数への(複数の)値の受け渡し(written by Taku Nakahara)

前回は、関数を作成して、それに対してひとつの変数を渡したり(引数)、受け取ったり(返り値)しました。
fig7_1
今回は、複数の値を渡したり、受け取ったりする方法をやろうと思います。
fig7_2
まず、複数個の値を渡す方法ですが、これは簡単です。

(shurrup_full_name.c)

#include <stdio.h>
  
void func(char *, char *);
  
int main(int argc, char *argv[])
{
    char *lastname, *familyname;
  
    familyname = argv[1];
    lastname = argv[2];
    func(familyname, lastname);
    return 0;
}

void func(char *sei, char *mei)
{
    printf("shurrup %s %s!\n", sei, mei);
}

しれっとポインタを使っていますが、まあ特にコメントはしません。なんとなく分かると思います。

次に、複数個の値を返してもらう方法ですが、return制御文ではひとつの値しか返すことができません。複数の値を返してもらうためには、ポインタを渡して、そのアドレスにある実体そのものを変更するという方法をとります。例によって理屈ぬきでちょっと実験してみましょう。

(multivars1.c)

#include <stdio.h>
  
void pass_vars(int, int);
 
int main()
{
    int v1 = 100;
    int v2 = 200;
 
    printf("initial values in main()\tv1=%d\tv2=%d\n", v1, v2);
    pass_vars(v1, v2);
    printf("processed values in main()\tv1=%d\tv2=%d\n", v1, v2);
    return 0;
}

void pass_vars(int v1, int v2)
{
    printf("initial values in pass_vars()\tv1=%d\tv2=%d\n", v1, v2);
    v1 = 300;
    v2 = 400;
    printf("processed values in pass_vars()\tv1=%d\tv2=%d\n", v1, v2);
}

実行結果は以下の通り

 initial values in main()        v1=100  v2=200
 initial values in pass_vars()   v1=100  v2=200
 processed values in pass_vars() v1=300  v2=400
 processed values in main()      v1=100  v2=200

この結果を見ると、main()関数から渡した値をmain()関数内からprintfすると、まったく変わっていません。しかし、呼び出し関数内でprintfした時には確かに変化していたようです。これはどういうことでしょうか?次のプログラムを実行してみてください。

(multivars2.c)

#include <stdio.h>

void pass_vars(int, int);
void pass_address(int *, int *);

int main()
{
    int v1 = 100;
    int v2 = 200;

    printf("initial values in main()\tv1=%d\tv2=%d\n", v1, v2);
    pass_vars(v1, v2);
    printf("values after pass_vars in main()\tv1=%d\tv2=%d\n", v1, v2);

    pass_address(&v1, &v2);
    printf("values after pass_address in main()\tv1=%d\tv2=%d\n", v1, v2);
    return 0;
}

void pass_vars(int v1, int v2)
{
    printf("initial values in pass_vars()\tv1=%d\tv2=%d\n", v1, v2);
    v1 = 300;
    v2 = 400;
    printf("processed values in pass_vars()\tv1=%d\tv2=%d\n", v1, v2);
}

void pass_address(int *v1, int *v2)
{
    printf("initial values in pass_address()\tv1=%d\tv2=%d\n", *v1, *v2);
    *v1 = 300;
    *v2 = 400;
    printf("processed values in pass_address()\tv1=%d\tv2=%d\n", *v1, *v2);
}

実行結果は

 initial values in main()        v1=100  v2=200
 initial values in pass_vars()   v1=100  v2=200
 processed values in pass_vars() v1=300  v2=400
 values after pass_vars in main()        v1=100  v2=200
 initial values in pass_address()        v1=100  v2=200
 processed values in pass_address()      v1=300  v2=400
 values after pass_address in main()     v1=300  v2=400

こちらのプログラムではmain()関数内でprintfをすると、pass_address()関数を呼び出した前と後で、与えた値が変わっています。なぜこういう動作をしたのかを理解するには、ポインタについて理解する必要があります。が、理屈をこねるよりも実際にプログラム中でどう使われているかを理解するほうがいいでしょう。

まず、関数pass_address()をmain()中で呼び出す際に

pass_address(&v1, &v2);

とやっています。&v1というのは、変数v1の「アドレス」を意味します。main()関数の中でv1の「値」は100ですが、この値がおいてあるメモリ上の住所が「アドレス」です。つまりmain()関数がpass_address()関数を呼び出して仕事を発注するときには100とか200とかの具体的な値を渡すのではなくて、「v1の住所がここで、v2の住所がここですよ。よろしく」という感じで、処理してほしい値のある「住所」を指定しています。

では、受け取るほうはどう受け取っているかというと、

void pass_address(int *v1, int *v2) { … }

という感じで受け取っています。int *v1というのは、int型の*v1という変数ではなくて「int型のアドレスの入る」ポインタ型の変数*v1ということを意味します。main()の中から投げられた&v1というアドレスはpass_address()関数では*v1に代入されます。*v1は単なるアドレスではなくそのアドレスの指定する実体を指し示す(pointする)もの(= pointer)なので、ポインタ変数を操作することは、そのポインタの指し示す実体を操作することと等価です。なので

*v1 = 300;

とやると、*v1で指し示しているアドレス(=これはmain()中のv1のアドレスと同じ)にある変数の実体を300にするということになります。

最後に、簡単な関数間で値を受け渡す(共有する)プログラムをこれ以上ないくらいに回りくどく説明して終わりにします。

(pointerMechanism.c)

#include <stdio.h>

void function(int, int *);
 
int main()
{
    int c1 = 100;
    int c2 = 120;

    printf("address: &c1 = %p  &c2 = %p\n", &c1, &c2);
    printf("value before function: c1 = %d  c2 = %d\n", c1, c2);

    function(c1, &c2);

    printf("value after function: c1 = %d  c2 = %d\n", c1, c2);
}

void function(int a, int *b)
{
    printf("address: &a = %p  &b = %p\n", &a, &b);
    printf("value: a = %d  b = %p\n", a, b);

    printf("initial real value in function: a = %d  *b = %d\n", a, *b);
    a *= 2;
    *b *= 2;
    printf("processed real value in function: a = %d  *b = %d\n", a, *b);
}

この実行結果が

 address: &c1 = 0xbffff5b8  &c2 = 0xbffff5bc
 value before function: c1 = 100  c2 = 120
 address: &a = 0xbffff598  &b = 0xbffff59c
 value: a = 100  b = 0xbffff5bc
 initial real value in function: a = 100  *b = 120
 processed real value in function: a = 200  *b = 240
 value after function: c1 = 100  c2 = 240

アドレスは、実行のたびに異なった場所に設定されるので、毎回違う値が出ます。このプログラムで変数のアドレスや実体がどのように変化したのかのフローが以下のようになります。
fig7_3
このようにして関数間ではアドレスを共有することによって複数の返り値を得ることができます。今日はこれで終わりです。