🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

Q&A

解決済

2回答

5779閲覧

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

DAISUKE45

総合スコア18

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

0グッド

0クリップ

投稿2020/12/14 01:52

現在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

c

1void freedblptr(char **dptr){ 2 printf("&dptr = %p\n", *dptr); 3 free(*dptr); 4 *dptr = NULL; 5 printf("&dptr = %p\n", *dptr); 6} 7 8int main() 9{ 10 char *test = (char *)malloc(sizeof(char) * (10 + 1)); 11 char *tmp = "1234567890"; 12 int i = 0; 13 while(i<10){ 14 test[i] = tmp[i]; 15 i++; 16 } 17 test[i] = '\0'; 18 printf("test = %s\n", test); 19 printf("test &ptr= %p\n", test); 20 21 freedblptr(&test); 22 23 printf("test = %s\n", test); 24 printf("test &ptr= %p\n", test); 25 26 return 0; 27}

shell

1$ clang -g -fsanitie=leak main.c 2$ ./a.out 3test = 1234567890 4test &ptr= 0x7fac88405d40 5&dptr = 0x7fac88405d40 6&dptr = 0x0 7test = (null) 8test &ptr= 0x0

main2.c

c

1void freeptr(char *ptr) 2{ 3 printf("&ptr = %p\n", ptr); 4 free(ptr); 5 ptr=NULL; 6 printf("&ptr = %p\n", ptr); 7} 8 9int main() 10{ 11 char *test = (char *)malloc(sizeof(char) * (10 + 1)); 12 char *tmp = "1234567890"; 13 int i = 0; 14 while(i<10){ 15 test[i] = tmp[i]; 16 i++; 17 } 18 test[i] = '\0'; 19 printf("test = %s\n", test); 20 printf("test &ptr= %p\n", test); 21 22 freeptr(test); 23 24 printf("test = %s\n", test); 25 printf("test &ptr= %p\n", test); 26 27 return 0; 28}

shell

1$ clang -g -fsanitie=leak main2.c 2$ ./a.out 3test = 1234567890 4test &ptr= 0x7fccd4c05d40 5&ptr = 0x7fccd4c05d40 6&ptr = 0x0 7test = 1234567890 8test &ptr= 0x7fccd4c05d40

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

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

C

1void F( int a ) 2{ a=777; } //値を変えた? 3 4int main() 5{ 6 int a = 5; 7 F( a ); //aを渡す 8 //ここでのaの値は5のまま 9 ... 10}

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

C

1void F( int *p ) 2{ *p=777; } //ポインタ値pが指し示す場所の値を変えた 3 4int main() 5{ 6 int a = 5; 7 F( &a ); 8 //ここでのaの値は777になった 9 ... 10}

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

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

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

投稿2020/12/14 01:56

編集2020/12/14 02:05
fana

総合スコア11990

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DAISUKE45

2020/12/14 03:12 編集

```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()が両方でうまく行っている理由は何なのでしょうか?
fana

2020/12/14 03:50

回答内の1つ目のコード例について,どうしてmain側のaの値が変化しないのか,わかりますか? > ptr=NULL; は,コード例の a=777; と同じことをしているわけですが.
DAISUKE45

2020/12/14 06:00

いじっているアドレスが違うので、変化しないというのは実行して試したので理解しました。 free()はどうなっているのでしょうか?
guest

0

ベストアンサー

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

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

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

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

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

c

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

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

c

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

投稿2020/12/14 05:03

ozwk

総合スコア13551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DAISUKE45

2020/12/14 06:08

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問