遍历双向链表永远运行(尽管明显的 NULL 终止符)

Walking through doubly linked list runs forever (despite apparent NULL terminator)

我对 C 双向链表的实现似乎无法正常工作。我担心这可能是由于我对 C 中的指针的粗略了解(来自对解释语言的幸福无知)。当我 运行 这段代码时,似乎 print_list() 运行 永远存在,即使它 应该 next 字段终止NULL.

#include<stdio.h>

typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

void print_list(Node* head) {
    Node* cursor = head;
    while(cursor != NULL) {
        printf("data: %d\n", cursor->data);
        cursor = cursor->next;
    }
}

void push_node(Node* head, int data) {  
    Node node = {data, NULL, NULL};
    Node* cursor = head;
    while(cursor->next != NULL) {       
        cursor = cursor->next;
    }
    cursor->next = &node;
    node.prev = cursor;
}

int main() {
    Node root = {0, NULL, NULL};
    push_node(&root, 1);
    push_node(&root, 2);
    print_list(&root);
    return 0;
}

我做错了什么??任何帮助将不胜感激。

您不能以这种方式创建节点

Node node = { data, NULL, NULL };

它将使用堆栈内存创建节点,一旦函数 returns 将被释放。您需要使用 malloc() 分配堆内存。

void push_node(Node* head, int data) {
    Node *node = malloc(sizeof(*node));
    if (node == NULL) {
        printf("memory error\n");
        return;
    }
    node->data = data;
    node->next = node->prev = NULL;

    Node* cursor = head;
    while (cursor->next != NULL) {
        cursor = cursor->next;
    }
    cursor->next = node;     // note the changes here
    node->prev = cursor;     //
}

而不是在 push 函数中创建局部变量,您应该使用动态分配的内存,使用 malloc 分配和 free 在退出 main 之前释放它,或者更好的是您可以使用 calloc,与 malloc 工作相同,但用零填充分配的内存,因此您不必关心填充空值,概念证明:

void push_node(Node* head, int data){
    Node* node = calloc(sizeof(Node), 1);
    node->data = data;
    Node* cursor = head;
    while(cursor->next != NULL) {       
        cursor = cursor->next;
    }
    cursor->next = node;
    node->prev = cursor;
}

并且在 return 之前的 main 中你应该释放分配的内存,函数应该类似于你的 print 函数但是你需要释放给定元素而不是打印数据直到你结束,概念验证:

void free_list(Node* head){
   if(head){
       free_list(head->next);
       free(head);
   }
}

退出前在您的名单上致电 free_list

您的代码不起作用,因为 Node node = { data, NULL, NULL }; 创建了一个作用域为函数 push_node 的局部变量 node。一旦该函数 returns,您将使用局部范围外的地址,这是未定义的行为。相反,使用 malloccalloc 为节点分配 space。

此外,您的 push_node 函数无法正确处理 head 指针为 NULL 的情况。有几个修复方法。一种方法是检查 NULL,如果是,return 新指针作为新的 head.

此外,您应该编写一个函数来在完成后删除您的列表。

Node *push_node(Node* head, int data) 
{  
    Node *node = malloc(sizeof(*node));
    node->data = data;
    node->prev = node->next = NULL;
    Node* cursor = head;
    if (cursor == NULL)
    {
        head = node;
    }
    else
    {
        while(cursor->next != NULL) {       
            cursor = cursor->next;
        }
        cursor->next = node;
        node->prev = cursor;
    }
    return head; 
}   

void free_list(Node *head) {
    if(head != NULL)
    {
        Node *next = head->next;
        free(head);
        head = next;
    }
}

int main()  {
    Node *root = NULL;
    root = push_node(root, 1);
    root = push_node(root, 2);
    print_list(root);
    free_list(root);
    return 0; 
}

push_node 中,您正在堆栈上创建新节点,在 push_node returns 之后,如果不调用未定义的行为(包括无限循环),内存将不再可读。您可以在 push_node 中使用 malloc,但您可能不应该这样做,除非您要创建一个巨大的列表。您还需要跟踪内存并在以后正确 free 它。如果您没有制作大量列表,您可以只在 main 中创建节点,只需对代码进行最少的更改:

#include<stdio.h>

typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

void print_list(Node* head) {
    Node* cursor = head;
    while(cursor != NULL) {
        printf("data: %d\n", cursor->data);
        cursor = cursor->next;
    }   
}

void push_node(Node* head, Node* new) {
    Node* cursor = head;
    while(cursor->next != NULL) {    
        cursor = cursor->next;
    }   
    cursor->next = new;                                                                                                                               
    new->prev = cursor;
}

int main() {
    Node root = {0,NULL,NULL};
    push_node(&root, &(Node){ 1, NULL, NULL }); 
    push_node(&root, &(Node){ 2, NULL, NULL }); 
    print_list(&root);
    return 0;
}

如果您的列表很大,那么无论如何您都应该使用 malloc,但是您不希望每次创建新节点时都调用它,相反,您可能会分配足够大的内存来容纳一个大列表一次调用 malloc,然后如果您的列表增长到您需要更多内存的程度,则将其重新分配为两倍大。

程序将永远运行,因为您对 push node 的调用将在堆栈上声明的 Node 两次放入列表中。您对同一事物使用了两个名称,

cursor->next = &node;

指向栈节点,为无限循环创造条件。这是在第二次调用此循环后发生的(在 push_node 中)。

while(cursor->next != NULL) {
    printf("ptr: %p -> %p\n",cursor,cursor->next); //add this
    cursor = cursor->next;
}

添加到您的调试打印指针值,您将看到发生了什么,

while(cursor != NULL) {
    printf("ptr: %p, data: %d\n",cursor,cursor->data); //change this
    cursor = cursor->next;
}

.