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

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

詳細はこちら
C

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

Q&A

解決済

3回答

2676閲覧

連結リストの初期化について

kyapi

総合スコア5

C

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

0グッド

0クリップ

投稿2020/12/12 07:04

連結リストのプログラムです
挿入、追加、削除の関数を作り、
switch文で数字を入力して
0=全てを初期化 1=追加 2=ひょうじ 3=削除 9=終了
ができるように実装しました。
0の初期化以外はうまくいきましたが、どうしてもfree関数が機能してくれません
例えば
1 で5、6の数字を追加し、
0 で初期化をし、
2 で表示をしても
初期化されずに5、6の数字が存在しています
free(p);
でなぜ解放されないのか理解できません
質問の内容がわかりにくい場合はお申し付けください

アドバイスよろしくお願いいたします
環境はEasyIDECです

コード #include <stdio.h> #include <stdlib.h> /* 変数headarによって連結リストの先頭が与えられている */ struct CELL { struct CELL *next; /* 次のデータが存在する場所を格納 */ int value; /* セルの値を格納 */ }header; /* 最後の要素は次にくる要素はないため,メンバnextにNULLポインタをセット */ /* 変数headerはリストの先頭を指しており,NULLのときリストは空である */ /* エラーメッセージをプリントしてexitする関数*/ /* ポインタ(アドレス)の先のデータを読み取り専用にする */ void fatal_error(const char *s) { /* fprintf関数を使ってstderrで標準エラーを出力する*/ fprintf(stderr,"%s", s); exit(1); /* 異常終了 */ } /* 追加、挿入する関数 引数aで値を受け取る */ /* 「構造体を指すポインタ->メンバ名」と「(*構造体を指すポインタ.メンバ名)」は同じ。 ドットは実体からアクセスするときに、 アローはポインタ(番地からアクセスするとき)につける。*/ void insert(int a) { /* 構造体のポインタを宣言 */ /* ポインタpは現在のセル,qはpが指すセルの直前のセルを指している */ struct CELL *p,*q,*new; /* 初期化:現在の場所を先頭にする */ p=header.next; /* 境界条件として先頭の次を指すようにしている */ q=&header; /* 先頭を指す */ /* 挿入すべき場所を探す このループを終えたとき、 ①pにはNULLが入っている=変数aの値が全ての要素よりも大きい場合 ②セルへのポインタが入っている=変数aよりも大きいか等しい場合 →新しい要素をpの直前に挿入する必要がある */ while(p!= NULL && a >p->value){ /* ポインタqがポインタpを追いかける形になる qが指すセルとpが指すセルの間に新しいセルが挿入される */ q=p; p=p->next; } /* malloc関数でメモリを確保した分だけnewに入れる */ /* 関数mallocの返す値がNULLであるかチェックする */ if((new=malloc(sizeof(struct CELL))) == NULL) fatal_error("メモリが足りません"); new->next = p; /* 現在の位置を新しいデータが存在する場所にする */ new->value = a; /* 新しいセルの場所に入力した値を入れる */ q->next = new; /* 挿入する場所に値を入れる */ } void print(void) { /* 構造体のポインタを宣言 */ /* 現在いる場所は先頭にしておく */ struct CELL *p; /* 境界条件として先頭の次を指すようにしている */ p=header.next; /* もし先頭がNULLだったら */ if(p == NULL){ fatal_error( "リストは空です\n" ); } /* NULLでない間値を表示する */ while(p!= NULL){ printf( " %d", p->value ); /* 次の値にする */ p = p->next; } printf("\n"); } /* 削除する関数 */ int DelCell(int c) { /* 構造体のポインタを宣言 */ /* ポインタpは現在、qはpが指すセルの直前のセルを指している */ struct CELL *p,*q; struct CELL *x=NULL; /* 初期化:現在の場所を先頭にする */ p=header.next; /* 境界条件として先頭の次を指すようにしている*/ q=&header; while(p!=NULL&&p->value!=c){ q=p; p=p->next; } /* もし先頭がNULLまたは指定した値の方が大きい場合 */ if(p==NULL || p->value<c){ /* 削除する値が見つからないとして1を返す */ free(x); return 1; /* 指定した値が見つかったら */ }else{ x=q->next; q->next=x->next; /* xで指定されたセルの内容を取り出して解放、削除 削除する値が見つかったとして0を返す*/ free(x); return 0; } } int main(void) { int i; /* ループ用変数 */ int n; /* 配列の要素数 */ int a,c; /* 追加,削除に使う変数 */ int x; /* 関数の戻り値用 */ int mn=0; /* メニュー選択用 */ struct CELL *p=NULL; struct CELL *q=NULL; q=&header; printf("連結リストをします\n"); do{ printf("メニューを選んでください\n0=全てを初期化 1=追加 2=ひょうじ 3=削除 9=終了\n"); scanf("%d",&mn); switch(mn){ case 0: printf("初期化します\n"); free(p); q=NULL; break; case 1: printf("新たに1つ数字を連結します。数字を入力してください\n"); scanf("%d",&a); insert(a); break; case 2: printf("リストを出します\n"); /* リストの要素の値を表示する */ print(); break; case 3: printf("リストの中の削除する数字を入力してください\n"); scanf("%d",&c); x=DelCell(c); if(x==1){ printf("NotExist\n"); } else{ printf("Done\n"); } break; case 9: printf("終了します\n"); break; default: printf("エラー:数字を入力してください\n"); scanf("%d",&mn); } }while (mn !=9); return 0; }

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

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

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

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

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

guest

回答3

0

ベストアンサー

freeは、使っていたメモリをシステムに返して、再利用するための関数です。
freeが返したメモリは、次のメモリ要求(mallocなど)で使われる可能性があります。
以下の例では、ポインタp1の指すメモリを解放し、mallocで新しい領域をとってポインタp2に入れたあと、p1を使ってデータを書き込んでいます。すると、変数p2の指すメモリを書き換えていないにもかかわらず、p2の指すメモリの中身が42から100に書き換わってしまいました。

原因は、最後の2行の出力を見ればわかります。アドレスが同じですね。これが再利用されたという意味です。
通常のmallocやfreeの実装ではこういう動作をするのが普通です。

linux

1$ cat test_free.c 2#include <stdio.h> 3#include <stdlib.h> 4 5int main() { 6 int *p1, *p2; 7 8 p1 = (int *)malloc(sizeof(int)); 9 free(p1); 10 p2 = (int *)malloc(sizeof(int)); 11 *p2 = 42; 12 printf("*p2 is %d\n", *p2); 13 *p1 = 100; 14 printf("*p2 is %d\n", *p2); 15 16 printf("p1 is 0x%016x\n", (long)p1); 17 printf("p2 is 0x%016x\n", (long)p2); 18} 19$ gcc -o test_free test_free.c 20$ ./test_free 21*p2 is 42 22*p2 is 100 23p1 is 0x0000000000bef010 24p2 is 0x0000000000bef010

投稿2020/12/12 13:37

ppaul

総合スコア24670

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

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

kyapi

2020/12/12 14:13

なるほど、そのような確認の仕方もできるのですね ありがとうございます!!
guest

0

main にある p はずっと NULL のままです。これを解放しても何も起こりません。なので p = &header とする必要があるでしょう。

ですが話はこれだけでは終わりません。これでは header にあるメモリだけが解放されて、あとに続いているリストのメモリが残ったままになります(メモリリークと呼びます)。これをきちんと解放してあげなければいけません。手順としては

  1. header へのポインタを得る。仮に current とする
  2. current->next を取っておく。仮に n としておく
  3. current を解放する
  4. currentn に置き換える
  5. currentNULL になるまで 2 ~ 4 を繰り返す

となります。手順を間違えるとわけわからんというふうになりがちなのがポインタの厄介なところですが、コンピュータの動作をかなり近いレベルで見ることができるので頑張ってみてください。

投稿2020/12/12 07:59

編集2020/12/12 11:22
A_kirisaki

総合スコア2853

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

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

kyapi

2020/12/12 09:00

/* メモリを解放する関数 */ void kaihou(void) { /* 先頭にしておく */ struct CELL *p=&header; /* 削除用の変数ポインタを作っておく */ struct CELL *current = p; /* NULLでない間繰り返す */ while( current!=NULL ){ current = current->next; /* 開放する */ free(current); } /* 一応先頭もNULLに初期化しておく */ p = NULL; } こんな感じで関数を作ってみました。 1,2,4の手順がよく理解できずにいます、ご指摘お願いします
A_kirisaki

2020/12/12 09:11

1 は「先頭にしておく」で OK です。2 は current->next を別のところに避難させないと current を解放した時次の場所がわからなくなってしまうからです。名前が同じなので紛らわしいですが n = current->next とかに読み替えてください。4 は次に読み込むものを current にセットします。これをしないと current NULL のままですから、次のループに移った時読み込むべきものがないということになります。
kyapi

2020/12/12 10:31

/* メモリを解放する関数 */ void kaihou(void) { /* ①先頭にしておく */ struct CELL *p=&header; struct CELL *current = p; /* 削除用の変数ポインタを作っておく */ struct CELL *n; /* ⑤NULLでない間繰り返す */ while( current!=NULL ){ /* ②仮に作っておく */ n=current->next; /* ③開放する */ free(current); /* ④次に読みこむものをセット */ current=p->next; } /* 一応先頭もNULLに初期化しておく */ p = NULL; } 丁寧に説明していただきありがとうございます! 実行はできるのですが動きません、どこがちがうのでしょうか、、
A_kirisaki

2020/12/12 10:36

current = p-> ではなく current = n です。退避しておいた次へのポインタを current に移し替えるわけです
kyapi

2020/12/12 10:55

/* メモリを解放する関数 */ void kaihou(void) { /* ①先頭にしておく */ struct CELL *n=&header; /* 削除用の変数ポインタを作っておく */ struct CELL *current = n; /* ⑤NULLでない間繰り返す */ while( current!=NULL ){ /* ②仮に作っておく */ current=n; /* ③開放する */ free(current); /* ④次に読みこむものをセット */ current=n->next; } /* 一応先頭もNULLに初期化しておく */ n = NULL; } このような感じでしょうか?? フリーズしてしまいます、nの定義は合ってますか??
A_kirisaki

2020/12/12 11:00

あ、current=n ではなく n = current->next ですね。見落としすみません
kyapi

2020/12/12 11:10

/* メモリを解放する関数 */ void kaihou(void) { /* ①先頭にしておく */ struct CELL *n=&header; /* 削除用の変数ポインタを作っておく */ struct CELL *current = n; /* ⑤NULLでない間繰り返す */ while( current!=NULL ){ /* ②仮に作っておく */ n = current->next ; /* ③開放する */ free(n); /* ④次に読みこむものをセット */ current=n->next; } /* 一応先頭もNULLに初期化しておく */ n = NULL; } ありがとうございます やってみましたがだめでした、、、
A_kirisaki

2020/12/12 11:15

どうして!どこで!free(n) しちゃうの!!
kyapi

2020/12/12 11:19

/* メモリを解放する関数 */ void kaihou(void) { /* ①先頭にしておく */ struct CELL *n=&header; /* 削除用の変数ポインタを作っておく */ struct CELL *current = n; /* ⑤NULLでない間繰り返す */ while( current!=NULL ){ /* ②仮に作っておく */ n = current->next ; /* 次に読みこむものをセット */ current=n->next; /* 開放する */ free(current); } /* 一応先頭もNULLに初期化しておく */ n = NULL; } すみません、混乱してきました 改善策をお願いいたします、
A_kirisaki

2020/12/12 11:23

順番をよーく確認しましょう!間違ってませんか?
kyapi

2020/12/12 11:37

/* メモリを解放する関数 */ void kaihou(void) { /* ①先頭にしておく */ struct CELL *n=&header; /* 削除用の変数ポインタを作っておく */ struct CELL *current = n; /* ⑤NULLでない間繰り返す */ while( current!=NULL ){ /* ②仮に作っておく */ /* ③開放する */ free(current); n = current->next ; /* ④次に読みこむものをセット */ current=n->next; } /* 一応先頭もNULLに初期化しておく */ n = NULL; } 確認してみましたが、、うまくいきません、助けてください
actorbug

2020/12/12 14:00

横から失礼します。 こちらの件ですが、もともとの回答の1.が間違っているように見えます。 1.でcurrentにheaderへのポインタを入れて、3.でcurrentを解放していますが、 headerはグローバル変数の領域に確保されているので、解放してはいけません。 1.は正しくは以下ではないでしょうか。 1. header.nextを取っておく。仮にcurrentとする
kyapi

2020/12/12 14:18

ありがとうございます!! 複雑にしなくても case 0: printf("初期化します\n"); free(p); q=NULL; header.next=NULL; break; これで初期化できました 解決しました助かります
actorbug

2020/12/12 22:11

それだとメモリリークの問題が解決しません。 A_kirisakiさんの処理を行わないと、追加・初期化を何度も繰り返したときに メモリを使い果たしてmallocがNULLを返すようになってしまいます。
kyapi

2020/12/13 00:35

/* メモリを解放する関数 */ void kaihou(void) {  struct CELL *current; struct CELL *n;   /* 先頭にしておく */ current=header.next; /* NULLでない間繰り返す */ while( current!=NULL ){ n = current->next; /* 開放する */ free(n); } /* 一応先頭もNULLに初期化しておく */ current = NULL; } こんな感じでしょうか?? しかし、このように複雑化すると、実行できてもフリーズしてしまいます
actorbug

2020/12/13 01:23 編集

もう自力で回答にたどり着くのは無理そうなので回答を貼ってしまいます。 /* メモリを解放する関数 */ void kaihou(void) { /* 1.header.nextを取っておく。仮にcurrentとする */ struct CELL* current = header.next; struct CELL* n; /* 5.current が NULL になるまで 2 ~4 を繰り返す */ while (current != NULL) { /* 2.current->next を取っておく。仮に n としておく */ n = current->next; /* 3.current を解放する */ free(current); /* 4.current を n に置き換える */ current = n; } /* 先頭をNULLに初期化しておく */ header.next = NULL; } (ごめんなさい、ここに書いていた内容は言い方がきつかったので削除しました)
kyapi

2020/12/13 01:32

/* メモリを解放する関数 */ void kaihou(void) { /* 削除用の変数ポインタを作っておく */ struct CELL *current; struct CELL *n; /* ①currentを先頭にしておく */ current=header.next /* ⑤currentがNULLになるまで②~④を繰り返す */ while (current != NULL) { /* ②解放したときに次の場所がわからなくなってしまうため, 次の場所を仮に避難させておく */ n = current->next; /* ③currentを解放する */ free(current); /* ④currenをnに置き換えて次の場所を指すようにする */ current = n; } /* 先頭をNULLに初期化しておく */ header.next = NULL; } 自分がわかりやすいように少し変えました すみません、考え方が頑固になっていたようです。 とても丁寧にありがとうございます、助かりました!!!!! きちんと初期化することができました
guest

0

freeをするとメモリ領域は開放されますが、その領域のデータは変わりません。
というより、開放して使うはずのない領域をわざわざ初期化することなど無駄でしかないためですね。

ということで、開放した領域から読み出すということ自体がアクセス違反なので、やってはいけないことです。

投稿2020/12/12 08:01

y_waiwai

総合スコア88038

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

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

kyapi

2020/12/12 10:13

アクセス違反なのですね、ありがとうございます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問