ポインタ変数を引数にした間接参照がエラーになるケース

色々と本やWebでポインタ関連の資料を読んではいるのですが、良く分からない挙動がありました。

int main (void) {
    int *num;
    
    num = (int *)malloc(sizeof(int));
    *num = 10;
    
    printf("%d",*num);
    return 0;
}

これはポインタ変数numにmallocでメモリを割り当ててデータのセットをしている処理です。

意図通りに「10」と表示されます。

しかしこれを自作関数でやろうとするとエラーで落ちます。

void set(int *num) {
    num = (int *)malloc(sizeof(int));
    *num = 10;
}

int main (void) {
    int *num;
    
    set(num);
    
    printf("%d",*num);
    return 0;
}

set関数までは動いてるようですが、「printf("%d",*num);」の部分でプログラムが強制終了してしまいます。

そこでアドレスはどうなってるのかを確認するためにこんなprintf文を入れてみました。

void set(int *num) {
    num = (int *)malloc(sizeof(int));
    printf("%p\n",num);
}

int main (void) {
    int *num;
    
    set(num);
    
    printf("%p\n",num);
    return 0;
}
$ main
00383148
00000001

set関数内ではメモリの確保に成功していますが、setから戻ってきた先のnumの値は初期化されてない状態のままでした。

これで少し見えてきました。

あくまで、あくまでも関数に渡された値そのものはコピーだということです。多分この解釈であってると思います。

関数にポインタを渡してはいますが、そのアドレスを保持しているポインタ変数(この場合num変数)そのものはコピーで渡されているということです。

つまりいくらset関数内の「num」変数を書き換えても呼び出し側に影響を与えることができないということになります。

呼び出し側に影響を与えるには「*num」で間接参照しないとダメなんです。

僕の書いたset関数はまず初めにmainから渡されたnumのアドレスを保持しているポインタ変数を、mallocで確保したメモリのアドレスで上書きしてしまってるだけなんですね。

ポインタの指し先ではなく、アドレスを保持しているポインタ変数そのものを上書きしてるだけなので、mainのnum変数には何の影響も与えていないという話です。

では、これをちゃんとmainのnum変数に影響を与えるにはどうしたらよいか?

set関数内では間接参照を行わないと、mainのnumに影響を与えることができないのでつまりmainのポインタ変数numのアドレスを渡せばよいということになります。

void set2(int **num) {
    // 間接参照してnumのアドレスを得て、そこに対してメモリ確保。
    *num = (int *)malloc(sizeof(int));
    **num = 10;
}

int main (void) {
    int *num;
    
    // ポインタ変数numのアドレスを渡す。
    set2(&num);

    printf("%d",*num);
    
    return 0;
}

これで完璧に動きました。

多分考え方はあってると思います。

ポインタってややこしいですが、割と首尾一貫した考え方に基づいているので慣れればうまく扱えるようになると思います。

もっともっとコードを書いて問題にブチ当たって少しずつ習得していきたいと思います。