😀

C言語で関数の引数に配列を値渡ししたい

2022/12/24に公開

久しぶりにC言語のお話になりますが、

関数の引数に配列を指定したい場合は、配列の先頭アドレスを渡すのが一般的です。
つまり、参照渡しを行うわけです。

配列を参照渡しする場合

例を出すと、

#include <stdio.h>

void funcB(char *b){
    printf("### funcB ###\n");
    *b = 1;
    printf("*b = %d, b = %p\n", *b, b);
    b++;
    *b = 1;
    printf("*b = %d, b = %p\n", *b, b);
    b++;
    *b = 1;
    printf("*b = %d, b = %p\n", *b, b);
}

void funcC(char c[3]){
    c[0] = 2;
    c[1] = 2;
    c[2] = 2;
    printf("### funcC ###\n");
    printf("c[0] = %d, &c[0] = %p\n", c[0], &c[0]);
    printf("c[1] = %d, &c[1] = %p\n", c[1], &c[1]);
    printf("c[2] = %d, &c[2] = %p\n", c[2], &c[2]);
}

void main(){
    char a[3] = {0, 0, 0};
    printf("### Init ###\n");
    printf("a[0] = %d, &a[0] = %p\n", a[0], &a[0]);
    printf("a[1] = %d, &a[1] = %p\n", a[1], &a[1]);
    printf("a[2] = %d, &a[2] = %p\n", a[2], &a[2]);

    funcB(a);
    printf("### After funcB ###\n");
    printf("a[0] = %d, &a[0] = %p\n", a[0], &a[0]);
    printf("a[1] = %d, &a[1] = %p\n", a[1], &a[1]);
    printf("a[2] = %d, &a[2] = %p\n", a[2], &a[2]);
    
    funcC(a);
    printf("### After funcC ###\n");
    printf("a[0] = %d, &a[0] = %p\n", a[0], &a[0]);
    printf("a[1] = %d, &a[1] = %p\n", a[1], &a[1]);
    printf("a[2] = %d, &a[2] = %p\n", a[2], &a[2]);
}

出力結果は下になります。
配列aと配列b、配列cのアドレスが変わっていないので、参照渡しになっていますね。
その証拠に、funcB, funcCで配列の中身の値を変更すると、配列aの値も変わっています。

funcB, funcCの仮引数の指定方法は異なっていますが、処理は同じです。

配列ではなく、通常の変数(char, intなど)の場合はfuncBのような書き方をすると参照渡し、
funcCのような書き方をすると値渡しになるのですが、配列の場合はどちらも参照渡しです。

では、配列を値渡ししたい場合はどうすればよいでしょうか?

### Init ###
a[0] = 0, &a[0] = 0x7fffc4584255
a[1] = 0, &a[1] = 0x7fffc4584256
a[2] = 0, &a[2] = 0x7fffc4584257
### funcB ###
*b = 1, b = 0x7fffc4584255
*b = 1, b = 0x7fffc4584256
*b = 1, b = 0x7fffc4584257
### After funcB ###
a[0] = 1, &a[0] = 0x7fffc4584255
a[1] = 1, &a[1] = 0x7fffc4584256
a[2] = 1, &a[2] = 0x7fffc4584257
### funcC ###
c[0] = 2, &c[0] = 0x7fffc4584255
c[1] = 2, &c[1] = 0x7fffc4584256
c[2] = 2, &c[2] = 0x7fffc4584257
### After funcC ###
a[0] = 2, &a[0] = 0x7fffc4584255
a[1] = 2, &a[1] = 0x7fffc4584256
a[2] = 2, &a[2] = 0x7fffc4584257

配列を値渡しする場合

構造体として渡す

#include <stdio.h>

typedef struct{
    char array[3];
}array_t;

void funcY(array_t y){
    y.array[0] = 1;
    y.array[1] = 1;
    y.array[2] = 1;
    printf("### funcY ###\n");
    printf("y.array[0] = %d, &(y.array[0]) = %p\n", y.array[0], &(y.array[0]));
    printf("y.array[1] = %d, &(y.array[1]) = %p\n", y.array[1], &(y.array[1]));
    printf("y.array[2] = %d, &(y.array[2]) = %p\n", y.array[2], &(y.array[2]));

}

void funcX(array_t *x){
    x->array[0] = 2;
    x->array[1] = 2;
    x->array[2] = 2;
    printf("### funcY ###\n");
    printf("x->array[0] = %d, &(x->array[0]) = %p\n", x->array[0], &(x->array[0]));
    printf("x->array[1] = %d, &(x->array[1]) = %p\n", x->array[1], &(x->array[1]));
    printf("x->array[2] = %d, &(x->array[2]) = %p\n", x->array[2], &(x->array[2]));

}

void main(){
    array_t z = {{0, 0, 0}};

    printf("### Init ###\n");
    printf("z.array[0] = %d, &(z.array[0]) = %p\n", z.array[0], &(z.array[0]));
    printf("z.array[1] = %d, &(z.array[1]) = %p\n", z.array[1], &(z.array[1]));
    printf("z.array[2] = %d, &(z.array[2]) = %p\n", z.array[2], &(z.array[2]));

    funcY(z);
    printf("### After funcY ###\n");
    printf("z.array[0] = %d, &(z.array[0]) = %p\n", z.array[0], &(z.array[0]));
    printf("z.array[1] = %d, &(z.array[1]) = %p\n", z.array[1], &(z.array[1]));
    printf("z.array[2] = %d, &(z.array[2]) = %p\n", z.array[2], &(z.array[2]));

    funcX(&z);
    printf("### After funcZ ###\n");
    printf("z.array[0] = %d, &(z.array[0]) = %p\n", z.array[0], &(z.array[0]));
    printf("z.array[1] = %d, &(z.array[1]) = %p\n", z.array[1], &(z.array[1]));
    printf("z.array[2] = %d, &(z.array[2]) = %p\n", z.array[2], &(z.array[2]));
}

配列を持つ構造体array_tを定義し、その構造体を渡すことによって値渡しができるようになります。
funcYのようにするということですね。
構造体zとyのアドレスが異なっていますし、yの配列に値を書き込んでもzには反映されません。

### Init ###
z.array[0] = 0, &(z.array[0]) = 0x7ffe88539f62
z.array[1] = 0, &(z.array[1]) = 0x7ffe88539f63
z.array[2] = 0, &(z.array[2]) = 0x7ffe88539f64
### funcY ###
y.array[0] = 1, &(y.array[0]) = 0x7ffe88539f48
y.array[1] = 1, &(y.array[1]) = 0x7ffe88539f49
y.array[2] = 1, &(y.array[2]) = 0x7ffe88539f4a
### After funcY ###
z.array[0] = 0, &(z.array[0]) = 0x7ffe88539f62
z.array[1] = 0, &(z.array[1]) = 0x7ffe88539f63
z.array[2] = 0, &(z.array[2]) = 0x7ffe88539f64
### funcY ###
x->array[0] = 2, &(x->array[0]) = 0x7ffe88539f62
x->array[1] = 2, &(x->array[1]) = 0x7ffe88539f63
x->array[2] = 2, &(x->array[2]) = 0x7ffe88539f64
### After funcZ ###
z.array[0] = 2, &(z.array[0]) = 0x7ffe88539f62
z.array[1] = 2, &(z.array[1]) = 0x7ffe88539f63
z.array[2] = 2, &(z.array[2]) = 0x7ffe88539f64

関数の仮引数にconstをつける

これについては値渡しではないですが、こちらを使用することのほうが多いと思います。
値渡しを行う目的は「呼び出し先の関数で値を変えられないようにする」ことがあります。
よって、値渡しを行わなくてもconstをつけることによって実現できます。

#include <stdio.h>
#include <string.h>

void funcD(const char d[3]){
    char d2[3];
    //Cannot write to the array
    //d[0] = 3;
    //d[1] = 3;
    //d[2] = 3;
    printf("### funcD ###\n");
    printf("d[0] = %d, &d[0] = %p\n", d[0], &d[0]);
    printf("d[1] = %d, &d[1] = %p\n", d[1], &d[1]);
    printf("d[2] = %d, &d[2] = %p\n", d[2], &d[2]);
    memcpy(d2, d, 3);
    printf("d2[0] = %d, &d2[0] = %p\n", d2[0], &d2[0]);
    printf("d2[1] = %d, &d2[1] = %p\n", d2[1], &d2[1]);
    printf("d2[2] = %d, &d2[2] = %p\n", d2[2], &d2[2]);
}

void main(){
    char a[3] = {0, 0, 0};

    printf("### Init ###\n");
    printf("a[0] = %d, &a[0] = %p\n", a[0], &a[0]);
    printf("a[1] = %d, &a[1] = %p\n", a[1], &a[1]);
    printf("a[2] = %d, &a[2] = %p\n", a[2], &a[2]);

    funcD(a);
    printf("### After funcD ###\n");
    printf("a[0] = %d, &a[0] = %p\n", a[0], &a[0]);
    printf("a[1] = %d, &a[1] = %p\n", a[1], &a[1]);
    printf("a[2] = %d, &a[2] = %p\n", a[2], &a[2]);
}

funcDの仮引数がconst char d[3]となっています。
配列の先頭ポインタを渡しているので参照渡しになっていますが、
constがついているので変更することはできません。
よって、値を変更したい場合はmemcpy等でコピーを作成してから行う必要があります。
当たり前ですが、配列dとd2は別物ですので、アドレスも異なります。

### Init ###
a[0] = 0, &a[0] = 0x7ffe44d0d065
a[1] = 0, &a[1] = 0x7ffe44d0d066
a[2] = 0, &a[2] = 0x7ffe44d0d067
### funcD ###
d[0] = 0, &d[0] = 0x7ffe44d0d065
d[1] = 0, &d[1] = 0x7ffe44d0d066
d[2] = 0, &d[2] = 0x7ffe44d0d067
d2[0] = 0, &d2[0] = 0x7ffe44d0d045
d2[1] = 0, &d2[1] = 0x7ffe44d0d046
d2[2] = 0, &d2[2] = 0x7ffe44d0d047
### After funcD ###
a[0] = 0, &a[0] = 0x7ffe44d0d065
a[1] = 0, &a[1] = 0x7ffe44d0d066
a[2] = 0, &a[2] = 0x7ffe44d0d067

関数の先で配列の中身を変えられたくない場合は、後半の解決方法のほうがシンプルでわかりやすいですが、
何かの諸事情で本当に「値渡し」をしたい場合は構造体とするという方法があります。

GitHubで編集を提案

Discussion