【问题标题】:linked list behavior - help me understand链表行为 - 帮助我理解
【发布时间】:2025-12-12 12:25:03
【问题描述】:

我正在学习链表,我不明白释放字符串时的行为变化。代码如下:

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

struct node {
    char*          data;
    struct node*   next;
    };

void Push(struct node** headRef, char *data)
{
    struct node* newNode = malloc(sizeof(struct node));
    newNode->data = data;
    newNode->next = *headRef;  // The '*' to dereferences back to the real head
    *headRef = newNode;        // ditto
}

int main(int argc, const char * argv[])
{

    char* auxStr;
    struct node* list;
    struct node* auxPtr;
    int i=5;


    while (i<9)
    {
        auxStr=malloc(sizeof("String:%d"));
        sprintf(auxStr, "String:%d",i);
        Push(&list, auxStr);
        i++;
    }

    auxPtr=list;

    i=0;
    while (auxPtr)
    {
        printf("Node:%d - Data:%s\n",i++,auxPtr->data);
        auxPtr=auxPtr->next;
    }

    return 0;
}

结果:

Node:0 - Data:String:8
Node:1 - Data:String:7
Node:2 - Data:String:6
Node:3 - Data:String:5

现在,当我第一次添加 free(auxStr) 时:

while (i<9)
{
    auxStr=malloc(sizeof("String:%d"));
    sprintf(auxStr, "String:%d",i);
    Push(&list, auxStr);
    free(auxStr);
    i++;
}

我现在明白了:

Node:0 - Data:String:8
Node:1 - Data:String:8
Node:2 - Data:String:8
Node:3 - Data:String:8

有人可以解释为什么吗?我知道它可能不是最有效的代码释放多次,但我看到了这种行为,这让我感到困惑。感谢您的帮助,帮助我更好地理解这个概念。

谢谢

【问题讨论】:

  • 请注意,当您的整数变为&gt;= 100 时,malloc(sizeof("String:%d")) 的缓冲区对于sprintf() 来说太小了。

标签: c pointers linked-list malloc free


【解决方案1】:

您将收到undefined behavior

您正在释放内存 (auxPtr),但您仍然碰巧有一个指向它的指针 - 如相关节点中的 data。这称为dangling reference

这个数据会发生什么是未定义的,它恰好为每个新分配重用相同的地址(但同样,任何事情都可能发生)。

因此,稍后打印数据时 - 输出未定义。

【讨论】:

  • 如果我使用数组代替:char auxStr[20];并摆脱 malloc 和 free,我仍然得到同样的错误结果。为什么会出现同样的情况?
  • @jelipito 不确定我在关注,您的意思是您将使用main() 中的数组并将其发送到链表吗?然后将完美定义结果 - 但再次错误,在这种情况下,所有节点中的 data 将具有分配给最后一个节点的值。您应该考虑为每个节点动态分配一个字符串,或者将每个node 中的char* 替换为char[SIZE](稍后注意缓冲区溢出)
  • 是的,好的,得到数组之一。我不明白的另一个是回到发布的原始代码,为什么它工作正常?当我多次为 auxStr 调用 malloc 时,它实际上每次都会创建不同的指针吗?
  • @jelipito 它可能会重用已经释放的内存(似乎是这种情况),并且由于您将字符串的 address 复制到 data - 在释放该地址之后您无法再次访问相同的地址(您可以,但这将导致未定义的行为)。当您稍后读取数据时,您访问的是已经释放的地址,结果是未定义的。在实践中,您不断获得相同的地址,这是内存分配器的有效行为 - 因为您已经释放了该内存。\
  • 谢谢,有没有推荐的学习动态分配的链接?。
【解决方案2】:

您不是通过 strncpy 复制数据字符串 - 相反,您只是将指针分配给稍后释放的相同字符串

【讨论】:

    【解决方案3】:

    here 所示,在释放指针后访问它是未定义的行为。

    【讨论】:

      【解决方案4】:

      struct node 中的每个数据都指向同一个地址。释放auxPtr 时,您正在访问一个不再分配的内存位置。在 C 中,它会导致未定义的行为。动态分配数据可能是一个更好的主意,如下所示。

      #include <assert.h>
      #include <stdlib.h>
      #include <string.h>
      
      void Push(struct node **head, const char *data, size_t size)
      {
        struct node *elt;
      
        elt = malloc(sizeof *elt);
        assert(elt != NULL);
      
        elt->data = malloc(size);
        assert(elt->data != NULL);
        memcpy(elt->data, data, size);
      
        elt->next = *head;
        *head = elt;
      }
      

      此外,您的列表中没有空指针。你应该先分配list

      【讨论】:

      • Hm .. 有趣的是它们为什么指向同一个位置,这在您的答案中没有涵盖。我认为是因为malloc() 再次返回刚刚释放的相同地址,但这只是猜测。