call by value OR call by reference


さて、 struct も出来たことだし、それをつかう関数でもつくりましょう。 で、ここで struct の実体を利用する場合と、pointer を利用した場合の比較をします。

その前に、typedef しておきましょう。先ほどの struct の型定義と同時に、 typedef することができます。 これで、 struct complexと書く代りに complex_tでOKですし、 complex_tp で、struct complexへのpointerという型を指すことになります。

typedef struct complex {
    double real;
    double imag;
} complex_t, * complex_tp;
結構便利なので、僕は構造体をつくると必ず typedef も一緒にするようにしています。

さて、準備も整ったところで、実際に関数をつくってみましょう(struct_B.c)。 つくるのは、複素数の足し算を行う関数です。 まずは、complex_t add_by_value(complex_t x, complex_t y);です。

complex_t add_by_value(complex_t x, complex_t y) {
    complex_t result;
    printf("add_by_value: x real %f imag %f  address %x\n", x.real, x.imag, &x); 
    printf("add_by_value: r real %f imag %f  address %x\n", result.real, result.imag, &result); 
    result.real = x.real + y.real;
    result.imag = x.imag + y.imag;
    return result;
}
struct を 2 個引数にとり、返り値も struct という関数です。 C では、引数を局所変数の一つと思ってくれて問題ありません。 つまり、呼出し側(caller)の struct と、呼び出された側(callee) の struct は同一物ではありません。 関数呼出しの際は、コピーをつくって呼び出された側(callee)に渡されます。 だから実行結果の x, a のアドレスを見比べると異なることに気づくはず。

    /* 呼出し側コード */
    printf("a real %f imag %f  address %x\n", a.real, a.imag, &a); 
    printf("c real %f imag %f  address %x\n", c.real, c.imag, &c);
    c = add_by_value(a,b);
    printf("c after real %f imag %f  address %x\n", c.real, c.imag, &c); 

/* 実行サンプル結果
a real 1.000000 imag 0.000000  address ffbef530
c real -1.000000 imag -1.000000  address ffbef510
add_by_value: x real 1.000000 imag 0.000000  address ffbef4f0
add_by_value: r real 0.000000 imag 0.000000  address ffbef460
c after real 3.000000 imag 1.500000  address ffbef510  */

さて、次は、pointer値を渡すようにしましょう。 まずは、void add_by_reference(complex_tp result, complex_tp x, complex_tp y);です。 x と y を足して、 result に格納します。 関数自体はこんな感じ。

void add_by_reference(complex_tp result, complex_tp x, complex_tp y) {
    printf("add_by_refer: x real %f imag %f  address %x\n", x->real, x->imag, x); 
    printf("add_by_refer: y real %f imag %f  address %x\n", y->real, y->imag, y);
    printf("add_by_refer: r real %f imag %f  address %x\n", result->real, result->imag, result); 
    result->real = x->real + y->real;
    result->imag = x->imag + y->imag;
    return;
}
/* 呼出し側コード
    printf("d real %f imag %f  address %x\n", d.real, d.imag, &d);
    add_by_reference(&d, &a, &b);
    printf("d after real %f imag %f  address %x\n", d.real, d.imag, &d);
 */
この場合、呼び出された側(callee)にわたるのは、&a, &b, &d という参照のみです。callee では、 参照を介してデータにアクセスすることで、内容を書き換えています。 というわけで、実行結果を眺めてもアドレスは共通です。


/* 実行サンプル結果
a real 1.000000 imag 0.000000  address ffbef530
d real -2.000000 imag -2.000000  address ffbef500
add_by_refer: x real 1.000000 imag 0.000000  address ffbef530
add_by_refer: r real -2.000000 imag -2.000000  address ffbef500
d after real 3.000000 imag 1.500000  address ffbef500*/

さて、二つの方法を見比べてきたわけですが、用途にあわせて好きな方を使ってください。 複素数の様に、一度つくったら書き換えないようなデータを使う場合は、 毎回コピーを取る方が素直かも知れません。 データの中身を利用するという意味で、call by value的です。

一方で、データをためておく場所で、呼び出された側でデータを更新して、 呼び出した側でその更新内容を利用するといった場合は、データの場所を伝えた方が素直でしょう。 データへの参照を渡すため、call by reference的です。


僕個人としては、call by reference 的なプログラムを普段は書いています。 でも "&" や "." はあまり好きではないので、こんな感じのコードを 書いています。

2001.11.14/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp