1

私は単一のリンクリストを破棄しようとしています。最初は、destroy関数のコードは次のようになっています。

void destroy_list_v0 (SLINK *list)
{   
SLINK ptr = *list;

while (NULL != *list)
    {
    ptr = *list;
    *list = (*list)->next;
    free (ptr);
    }
}

関数v0は完璧に機能します。ここに出力があります。

Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 5.
The 2nd element is 93.
The 3rd element is 92.
The 4th element is 70.
The 5th element is 92.
traverse_end & destroy_start.
destroy_end & traverse_start.
traverse_end.
All operations done.

次に、シングルポンターで十分だと思ったので、関数をシングルポインターバージョンに調整します。

void destroy_list_v1 (SLINK list)
{
SLINK ptr = list;   
while (NULL != list)
    {
    ptr = list;
    list = list->next;
    free (ptr);
    }
}

v1の出力は次のとおりです。

Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 27.
The 2nd element is 38.
The 3rd element is 20.
The 4th element is 66.
The 5th element is 30.
traverse_end & destroy_start.
destroy_end & traverse_start.
The 0th element is 0.
The 1st element is 32759808.
The 2nd element is 32759968.
The 3rd element is 32759936.
The 4th element is 32759904.
The 5th element is 32759872.
traverse_end.
All operations done.

破棄機能が正常に機能していることを確認するために、破棄された後にリンクリストをトラバースします。すべてのノードの値が変更され、不確定であるにもかかわらず、リストを読み取ることができることがわかりました(v0の場合は読み取ることができませんでした)。v0が実行された後、リストのポインターはNULLを指していると思いましたが、v1が実行された後も、元のアドレスを指します。このアイデアをテストするために、v0をv2に調整します。

void destroy_list_v2 (SLINK *list)
{   
SLINK p_list = *list;
SLINK ptr = *list;
while (NULL != *p_list)
    {
    ptr = *p_list;
    p_list = p_list->next;
    free (ptr);
    }
}

v2の出力は次のとおりです。

Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 76.
The 2nd element is 53.
The 3rd element is 80.
The 4th element is 31.
The 5th element is 97.
traverse_end & destroy_start.
destroy_end & traverse_start.
The 0th element is 0.
The 1st element is 13860864.
The 2nd element is 13861024.
The 3rd element is 13860992.
The 4th element is 13860960.
The 5th element is 13860928.
traverse_end.
All operations done.

私の分析は正しいと思いますが、それは新しい疑問につながります。

ノード構造体はここにあります:

typedef struct tag_node
{
int elem;
struct tag_node *next;
}NODE, *SLINK; //SLINK means SINGLE LINK

2つの質問があります:

1:ポインタ'next'は、現在のポインタが指すメモリ空間に格納されます。現在のノードを解放した後、ポインタ'next'のメモリ空間をまだ読み取ることができるのはなぜですか。ポインタ「次へ」はまだ生きていますか?この質問があるのは、v1またはv2が実行された後、読み取ることができるのはヘッダーノードのみである必要があると考えたためです。

2:v1またはv2が実行された後、v1およびv2がリスト全体を破棄するのに、なぜヘッダーの値がまだ残っているのですか?不定の数字に変わったのは1日から5日くらいのはずだと思いました。

これがコード全体であり、環境はDebian、clangです:

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

#define     i_track(n)  printf ("The %s's is %d.\n", #n, (n))
#define     s_track(n)  printf ("%s.\n", #n);

typedef struct tag_node
{
int elem;
struct tag_node *next;
}NODE, *SLINK; //SLINK means SINGLE LINK

void  node_track (SLINK list);

NODE *node_generate (void);
SLINK init_list (int len);
SLINK locate_cur (SLINK list, int target_elem);
void insert_node (SLINK *list, int new_elem, int tag_elem);
SLINK traverse_list (SLINK list);
void clear_list (SLINK list);
void destroy_list_v0 (SLINK *list);
void destroy_list_v1 (SLINK list);
void destroy_list_v2 (SLINK *list);
void list_info (SLINK list, int node_elem);

int main (int argc, char *argv[])
{
int len;
SLINK list;
printf ("Input a number for length of the link.\n");
scanf ("%d", &len);
s_track(init_start);
list = init_list (len);
s_track(init_end & traverset_start);
traverse_list (list);
s_track(traverse_end & destroy_start);
//  destroy_list_v0 (&list);
//  destroy_list_v1 (list);
destroy_list_v2 (&list);
s_track(destroy_end & traverse_start);
traverse_list (list);
s_track(traverse_end);
s_track(All operations done);

return EXIT_SUCCESS;
}               /* ----------  end of function main  ---------- */

NODE *node_generate (void)
{
NODE *new_node = malloc (sizeof (NODE));
if (NULL == new_node)
{
    printf ("ERROR: malloc failed.\n");
    exit (EXIT_FAILURE);
}
memset (new_node, 0, sizeof(NODE));
return new_node;
}

SLINK locate_cur (SLINK list, int target_elem)
{
NODE *prev, *cur;
prev = node_generate ();
cur = node_generate ();
for (prev = list, cur = list->next; NULL != cur && target_elem != cur->elem; prev = cur, cur = cur->next)
    ;
return cur;
}

void insert_node (SLINK *list, int new_elem, int tag_elem)
{
NODE *new_node = node_generate ();
NODE *cur = *list;
new_node->elem = new_elem;
if ((int)NULL == tag_elem)
{
    new_node->next = (*list)->next;
    (*list)->next = new_node;
}
else
{
    *list = locate_cur (cur, tag_elem);
    new_node->next = (*list)->next;
    (*list)->next = new_node;
}
}

SLINK init_list (int len)
{
SLINK header = node_generate ();
srand ((unsigned) time(0));
int elem;
for (int i = 0; i < len; i++)
    {
        elem = rand () % 100;

        if (4 == elem / 10)
        {
            elem = elem + 50;
        }
        if (4 == elem % 10)
        {
            elem = elem + 5;
        }
        if (0 == elem % 100)
        {
            elem = elem + 999;
        }
        insert_node (&header, elem, (int)NULL); 
    }
return header;
}

void clear_list (SLINK list)
{
for (SLINK cur = list->next; NULL != cur; )
{
    cur = cur->next;
    free (list->next);
    list->next = cur;
}
}

void destroy_list_v0 (SLINK *list)
{   
SLINK ptr = *list;

while (NULL != *list)
{
    ptr = *list;
    *list = (*list)->next;
    free (ptr);
}
}

void destroy_list_v1 (SLINK list)
{   
SLINK ptr = list;

while (NULL != list)
{
    ptr = list;
    list = list->next;
    free (ptr);
}
}

void destroy_list_v2 (SLINK *list)
{
SLINK p_list = *list;
SLINK ptr = *list;
while (NULL != p_list)
{
    ptr = p_list;
    p_list = p_list->next;  
    free (ptr);
}
}

SLINK traverse_list (SLINK list)
{
SLINK ptr = list;
for (int node_num = 0; ptr != NULL; ptr = ptr->next)
{
    list_info (ptr, node_num);
    ++node_num;
}
return list;
}

void list_info (SLINK list, int node_num)
{
if (1 == node_num % 10 && 11 != node_num)
{
    printf ("The %dst element is %d.\n", node_num, list->elem);
}
else if (2 == node_num % 10 && 12 != node_num)
{
    printf ("The %dnd element is %d.\n", node_num, list->elem);
}
else if (3 == node_num % 10 && 13 != node_num)
{
    printf ("The %drd element is %d.\n", node_num, list->elem);
}
else
{
    printf ("The %dth element is %d.\n", node_num, list->elem);
}
}

void node_track (NODE *flag)
{
printf ("The flag element is %d.\n", flag->elem);
}
4

5 に答える 5

4

メモリを解放することは、ポインタ変数に含まれるアドレスを変更することと同じではありません。

freeを呼び出すと、 mallocによって管理されるヒープにメモリが解放されます。以前に割り当てたメモリを指している変数がある場合は、解放操作の後もそこを指していますただし、free の後にポインターを使用するのはバグです。

リンクされたリストが解放されたメモリをまだ指していないことを確認したい場合は、関連付けられたメモリが解放された後、構造内の各ポインターに NULL を割り当てることができます。

于 2012-12-04T16:09:57.877 に答える
2

Cでこれに慣れてください。「動作は未定義です」というフレーズは、すぐに慣れることになるマントラです。つまり、特定のことを行うと、クラッシュから明らかに完璧な動作に至るまで、あらゆることにつながる可能性があります。

ポインターは、このマントラの典型的な例です。メモリを解放しても、引き続きアクセスできますか?まあ、それは未定義です。待ってください、それは夏時間です、そして今それは墜落しましたか?まあそれは未定義です。待ってください、あなたはそれをWindowsで実行しました、そしてそれはYで終わる日を除いてうまく働きますか?まあそれは未定義です。

マントラを覚えておいてください。それはあなたによく役立つでしょう。あなたが何か間違ったことをしたときにCが大声で不平を言うことを期待することは間違った期待であり、これを心に留めておくことはあなたに多くの悲しみと涙を救うことができます。

于 2012-12-04T16:15:57.790 に答える
1
  1. 何でもできる C の国へようこそ (合法でなくても)。あなたがしたことは、未定義の動作を呼び出すことです。削除されたメモリにアクセスすることはもう許可されていませんが、誰もあなたの試みを止めていません. C は、実際に有効なメモリにアクセスしているかどうかをチェックしません。何か間違ったことをするように指示したとしても、それはただ先に進み、あなたが指示したことを実行します。それが「機能した」という事実は、運と実装で定義されたものの混合です。このようなバグは、クラッシュする代わりにプログラムが続行され、しばらくしてから最終的にクラッシュし始めるまでバグが見つからないため、見つけるのが困難です。未定義の動作 (削除されたメモリにアクセスしたときに発生する動作) を呼び出すと、ブラック ホールが開いて私たち全員を飲み込むことから、プログラムのクラッシュまで、あらゆることが起こります。

  2. v1リスト全体を破棄しv2ますが、メモリを解放しても、メモリ内の値も消去されるわけではありません。値はまだそこにありますが、それらのメモリ バケットを OS に戻したので、それらにアクセスすることはできなくなりました。バケットはまだ値を保持しています。彼らはもうあなたのものではありません。

于 2012-12-04T16:15:06.873 に答える
0

C99 言います:

free 関数は、ptr が指すスペースの割り当てを解除します。つまり、さらに割り当てられるようにします。ptr がヌル ポインターの場合、アクションは発生しません。それ以外の場合、引数が calloc、malloc、または realloc 関数によって以前に返されたポインターと一致しない場合、または空間が free または realloc の呼び出しによって解放された場合、動作は未定義です。

于 2012-12-04T16:21:45.283 に答える
0

free() はバッファをフリーとしてマークします。これは、後続の malloc() が同じ日付領域を使用できることを意味します。これは、消去されることなどを意味するものではありませんが、オペレーティング システムに返される可能性があります (したがって、それにアクセスする可能性があります)。セグメンテーション違反を引き起こす可能性があります)。

解放されたメモリにアクセスするのはバグです。通常はそのまま残っている場合でも、他の関数で使用されているメモリにアクセスしている可能性があるからです。

于 2012-12-04T16:14:03.577 に答える