为什么仍然可以读取被破坏的链表

时间:2012-12-04 16:06:26

标签: c linked-list

我正在尝试销毁单链表,首先,我的销毁函数代码如下:

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.

然后我认为单个ponter就足够了,所以我将函数调整为单指针版本:

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

我有两个问题:

1:指针'next'存储在当前指针所指向的内存空间中,在自由当前节点之后,为什么仍然可以读取指针'next'的内存空间?指针'下一个'还活着吗?我有这个问题,因为我认为在v1或v2执行之后,它应该只是可以读取的标题节点。

2:我v1和v2破坏了整个列表,在v1或v2执行后,为什么header的值仍然是?我认为它应该像第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);
}

5 个答案:

答案 0 :(得分:4)

释放内存与更改指针变量中包含的地址不同。

free 的调用将内存释放回 malloc 管理的堆。如果你的变量仍然指向你之前分配的内存,那么在空闲操作之后它仍然仍然指向那里。但是,在free之后使用指针进行任何操作都是一个错误。

如果要确保链表仍未指向已释放的内存,则可以在释放相关内存后为结构中的每个指针指定NULL。

答案 1 :(得分:2)

在C中习惯这句话。“行为未定义”这句话是你很快习惯的口头禅,这意味着做某些事情可能会导致从崩溃到明显完美行为的任何事情。

指针是这个口头禅的经典案例。你释放了内存,仍然可以访问它?嗯,这是未定义的。等等,这是夏令时,现在它崩溃了?好吧,这是未定义的。等等,你在Windows上运行它并且它工作正常,除了在Y结尾的日子?好吧,这是未定义的。

记住口头禅;它会很好地为你服务。当你做错事时,期待C大声抱怨是错误的期望,记住这一点可以为你节省多少悲伤和泪水。

答案 2 :(得分:1)

  1. 欢迎来到C的土地,在那里你可以做任何事情(即使它不合法)。你所做的是调用未定义的行为。您不能再访问已删除的内存,但没有人阻止您尝试。 C不会检查您确实正在访问有效内存。即使你告诉它做错了什么,它就会继续前进并做你所说的。它“工作”的事实是运气和实现定义的东西的混合物。像这样的错误很难找到,因为它不会崩溃,而是程序会继续,并且直到很久之后才会发现错误,直到它最终开始崩溃。一旦你调用未定义的行为(当你访问已删除的内存时会发生这种情况),任何事情都可能发生,从黑洞开放,吞噬我们所有人,程序崩溃,到看起来工作得很好的程序。

  2. v1v2会破坏整个列表,但释放内存并不意味着您也会删除内存中的值。值仍然存在,您不再允许访问它们,因为您已将这些内存桶返回给操作系统。水桶仍然保持价值。他们不再是你的了。

答案 3 :(得分:0)

free()将缓冲区标记为空闲,这意味着后续的malloc()可以使用相同的日期区域,这并不意味着它将被删除或任何东西,但它可以返回到操作系统(和因此访问它可能会导致分段错误。

访问释放的内存是一个错误,因为即使它仍然没有被触及,你也可以访问其他一些函数使用的内存。

答案 4 :(得分:0)

C99说:

free函数导致ptr指向的空间被释放,即可用于进一步分配。如果ptr是空指针,则不执行任何操作。 否则,如果参数与之前由calloc,malloc或realloc函数返回的指针不匹配,或者如果通过调用free或realloc释放了空间,则行为未定义。 < / p>