You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言链表意外修改问题:指针操作与内存释放异常排查

链表操作问题:修改临时指针不影响原链表的解决方案

问题现象

  • 初始输出doc_lists[0]为338216 338217 379830,符合预期
  • 将temp_result指向doc_lists[0]并释放其节点后,再次输出doc_lists[0]得到0 3这类非预期值
  • 后续若再次释放doc_lists[0]会触发double free错误;不释放则多次运行可能出现segmentation fault
  • 尝试过“指针初始化NULL”“free后设为NULL”的方案,未解决问题

重现代码

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

// 定义DocNode结构
struct DocNode {
    int doc_id;
    struct DocNode* next;
};

// 创建新的节点
struct DocNode* create_node(int doc_id) {
    struct DocNode* new_node = (struct DocNode*)malloc(sizeof(struct DocNode));
    new_node->doc_id = doc_id;
    new_node->next = NULL;
    return new_node;
}

int main() {
    // 定义doc_lists数组,假设有两个链表
    int query_count = 2;
    struct DocNode* doc_lists[2];

    // 初始化第一个链表: 338216 -> 338217 -> 379830
    doc_lists[0] = create_node(338216);
    doc_lists[0]->next = create_node(338217);
    doc_lists[0]->next->next = create_node(379830);

    // 初始化第二个链表: 123456 -> 789012
    doc_lists[1] = create_node(123456);
    doc_lists[1]->next = create_node(789012);

    // 输出每个链表的内容
    for (int i = 0; i < query_count; i++) {
        printf("doc[%d] is: ", i);
        struct DocNode* doc_list = doc_lists[i];
        while (doc_list) {
            printf("%d ", doc_list->doc_id);
            doc_list = doc_list->next;
        }
        printf("\n");
    }

    // 遍历并释放链表1中的节点
    struct DocNode* temp_result = doc_lists[0];
    printf("\nReleasing temp_result:\n");
    while (temp_result) {
        struct DocNode* temp = temp_result;
        temp_result = temp_result->next;
        printf("%d ", temp->doc_id);
        free(temp);
    }
    printf("\n");

    // 再次尝试输出链表内容
    printf("\nTo check the result:\n");
    for (int i = 0; i < query_count; i++) {
        printf("doc[%d] is: ", i);
        struct DocNode* doc_list = doc_lists[i];
        while (doc_list) {
            printf("%d ", doc_list->doc_id);
            doc_list = doc_list->next;
        }
        printf("\n");
    }

    return 0;
}

核心需求

希望用temp_list指向doc_lists[0],对temp_list的节点删除操作不影响原doc_lists[0],同时顾虑深拷贝在大数据场景下的性能开销。

解决方案

1. 本质原因:指针共享内存

temp_result = doc_lists[0]只是让临时指针指向了原链表的首节点地址,两者共享同一块内存空间。释放temp_result遍历到的节点,本质就是释放原链表的节点,原链表的指针自然变成了指向已释放内存的野指针,后续访问就会出现异常。

2. 针对性方案

方案A:深拷贝链表(适合必须修改临时链表的场景)

如果确实需要对临时链表执行删除/修改操作,同时保留原链表,只能对原链表做深拷贝。为了优化大数据场景的性能,可以考虑:

  • 按需拷贝:只拷贝需要操作的部分节点,而非整个链表
  • 使用内存池:提前申请一批节点内存,减少malloc/free的开销
  • 延迟释放:如果临时链表使用后就销毁,可以和原链表共享内存,用引用计数机制管理(但实现复杂度较高)

示例深拷贝函数:

struct DocNode* deep_copy_list(struct DocNode* head) {
    if (!head) return NULL;
    struct DocNode* new_head = create_node(head->doc_id);
    struct DocNode* curr = new_head;
    struct DocNode* orig_curr = head->next;
    while (orig_curr) {
        curr->next = create_node(orig_curr->doc_id);
        curr = curr->next;
        orig_curr = orig_curr->next;
    }
    return new_head;
}

使用时:

struct DocNode* temp_result = deep_copy_list(doc_lists[0]);
// 此时释放temp_result的节点不会影响原链表

方案B:只读遍历,避免修改/释放(性能最优)

如果你的操作只是遍历链表、读取数据,不需要删除节点,那完全不需要拷贝,直接用临时指针遍历即可,不要执行free操作。这样既不会影响原链表,也没有任何性能开销。

方案C:标记删除(适合批量操作场景)

如果需要模拟“删除”但不想真正释放内存,可以给链表节点加一个is_deleted标记位,临时链表操作时只修改标记,原链表需要访问时过滤掉标记为删除的节点。这种方式不需要拷贝内存,性能极高,适合大数据场景。
修改结构体:

struct DocNode {
    int doc_id;
    int is_deleted; // 0表示未删除,1表示已删除
    struct DocNode* next;
};

创建节点时初始化is_deleted = 0,临时操作时只需设置temp->is_deleted = 1,原链表遍历跳过is_deleted == 1的节点即可。


内容的提问来源于stack exchange,提问作者user23883251

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:54:53