質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.37%

C言語のダブルポインターの使い方がわからない

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,063

score 18

現在C言語のポインタに関して勉強しているのですが、下記のコードを実行したところ、

ダブルポインターを関数の引数にしたコードは、freeしたあとに、NULL埋めを行うと、関数を抜けたあとも、きちんと(null)という実行結果を得られたのですが、ポインターを関数の引数にしたコードでは、関数を抜けても1234567890が残ったままになっていました。

配列を関数の引数に取るとポインタの先頭のアドレスを渡すので、NULL埋めを行っても同じ結果になるのではないかなと思っていました。

そこで質問なのですが、

  1. なぜその様になるのかの理由を教えてもらいたいです。
  2. なぜfreeはうまくいってNULL埋めがうまく行かないのか?

上記の2点お答えいただきたいです。
よろしくおねがいします。

実行環境は、

clang version 11.0.0
Target: x86_64-apple-darwin20.1.0
Thread model: posix
InstalledDir: /usr/local/Cellar/llvm/11.0.0/bin

llvmを使ってメモリリークチェックを行いました。
Mac OS

main.c

void freedblptr(char **dptr){
    printf("&dptr = %p\n", *dptr);
    free(*dptr);
    *dptr = NULL;
    printf("&dptr = %p\n", *dptr);
}

int main()
{
    char *test = (char *)malloc(sizeof(char) * (10 + 1));
    char *tmp = "1234567890";
    int i = 0;
    while(i<10){
        test[i] = tmp[i];
        i++;
    }
        test[i] = '\0';
    printf("test = %s\n", test);
    printf("test &ptr= %p\n", test);

    freedblptr(&test);

    printf("test = %s\n", test);
    printf("test &ptr= %p\n", test);

    return 0;
}
$ clang -g -fsanitie=leak main.c
$ ./a.out
test = 1234567890
test &ptr= 0x7fac88405d40
&dptr = 0x7fac88405d40
&dptr = 0x0
test = (null)
test &ptr= 0x0

main2.c

void freeptr(char *ptr)
{
    printf("&ptr  = %p\n", ptr);
    free(ptr);
    ptr=NULL;
    printf("&ptr  = %p\n", ptr);
}

int main()
{
    char *test = (char *)malloc(sizeof(char) * (10 + 1));
    char *tmp = "1234567890";
    int i = 0;
    while(i<10){
        test[i] = tmp[i];
        i++;
    }
        test[i] = '\0';
    printf("test = %s\n", test);
    printf("test &ptr= %p\n", test);

    freeptr(test);

    printf("test = %s\n", test);
    printf("test &ptr= %p\n", test);

    return 0;
}
$ clang -g -fsanitie=leak main2.c
$ ./a.out
test = 1234567890
test &ptr= 0x7fccd4c05d40
&ptr  = 0x7fccd4c05d40
&ptr  = 0x0
test = 1234567890
test &ptr= 0x7fccd4c05d40
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+3

以下のコードで,関数Fにaを引数として渡しましたが,注釈に記したように,main内のaの値は変更されません.
引数の型がポインタでも同じことです.

void F( int a )
{ a=777; }  //値を変えた?

int main()
{
  int a = 5;
  F( a );  //aを渡す
  //ここでのaの値は5のまま
  ...
}

引数にポインタを渡す例:

void F( int *p )
{ *p=777; }  //ポインタ値pが指し示す場所の値を変えた

int main()
{
  int a = 5;
  F( &a );
  //ここでのaの値は777になった
  ...
}

int *は,int型の場所を指し示すから,「その指し示す場所の値を変える」というのは,指し示されているint型の値を変えることになる.

ダブルポインタでも話は一緒.
int **なら,int*型の場所を指し示すから,「その指し示す場所の値を変える」というのは,指し示されているint*型の値を変えることになる.

質問のコードでは,ダブルポインタを渡した側では(それが指し示す)ポインタの値を変えている.

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/12/14 12:10 編集

    ```c
    void freedblptr(char **dptr){
    printf("&dptr = %p\n", *dptr); // 1番目
    free(*dptr);
    *dptr = NULL;
    printf("&dptr = %p\n", *dptr);
    }

    void freeptr(char *ptr)
    {
    printf("&ptr = %p\n", ptr); // 2番目
    free(ptr);
    ptr=NULL;
    printf("&ptr = %p\n", ptr);
    }

    ```

    一番目と二番目で指し示しているアドレスが同じなので、同じのをいじっているというわけではないのですか?

    また、free()が両方でうまく行っている理由は何なのでしょうか?

    キャンセル

  • 2020/12/14 12:50

    回答内の1つ目のコード例について,どうしてmain側のaの値が変化しないのか,わかりますか?

    > ptr=NULL;

    は,コード例の a=777; と同じことをしているわけですが.

    キャンセル

  • 2020/12/14 15:00

    いじっているアドレスが違うので、変化しないというのは実行して試したので理解しました。

    free()はどうなっているのでしょうか?

    キャンセル

checkベストアンサー

+1

関数を呼び出すとき、引数はコピーされて関数に渡ります。
ポインタでも一緒です。

さて、質問文の実行結果ではちゃんとしたアドレスが見えてますが、
分かりづらいのでここではmallocの返り値が0x1000で、
変数test自体のアドレス&test0x2000だったとします。

質問者さんがやりたいことは0x1000freeして、0x2000をNULLにすることです。

アドレス 内容
0x1000    '1'  = testの格納する値
0x1001    '2'  = test[1]の...
... ... ...
0x2000 0x1000  = &testの...

さて、freedblptrではdptr&test = 0x2000を渡してますので

void freedblptr(char **dptr){ //dptr = 0x2000
    free(*dptr); // *dptr=0x1000 をfreeする
    *dptr = NULL; // dptr=0x2000を参照してNULLに書き換える
                  // 結果として同じく0x2000を見ているtestが書き換わる
}

一方freeptrではptrtest = 0x1000を渡してますので

void freeptr(char *ptr){ //ptr = 0x1000
    free(ptr); // ptr=0x1000 をfreeする
    ptr = NULL; // ptr=0x1000だったのをptr=NULLにするだけ、
                // 0x2000は触ってないので当然testも書き換わらない
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/12/14 15:08

    丁寧に図解していただきありがとうございます!!!

    変数自体のアドレスをいじってないということを理解できてなかったみたいです。
    完全に考え方を誤解していました。
    free(ptr)のあとにptr=NULLとしたい場合は、ダブルポインターで取らないといけないんですね。
    freeptrの内部でやっていたのは、仮引数で取られたptrをいじっていたので、test=NULLになってないということですね。

    free()ではなぜうまくいっているのかも理解できました。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る