C语言union类型双链表旧值被覆盖问题排查与解决
问题描述
需求为从文件读取结构化数据,构建以union联合体为节点数据类型的双向链表,以下核心结构定义保持固定不修改:
// 支持存储int整型、double浮点型、char*字符串三种类型的联合体 typedef union val { int int_val; double dbl_val; char *str_val; } val_t; // 带前驱、后继指针的双向链表节点 typedef struct node { struct node *prev; struct node *next; val_t *vals; } node_t; // 链表结构,存储头尾节点与元信息 typedef struct list { node_t *head; node_t *tail; size_t size; } list_t; // 表头结构,存储字段数量、类型、名称 typedef struct header { int field_cnt; int *field_type; char **field_name; } header_t;
初始实现包含尾插法节点插入函数insert、逐行解析文件内容的read_file函数,运行时出现稳定复现的异常:每次向链表插入新节点时,之前已经插入的历史节点存储的值会被异常篡改,最终遍历链表时所有节点的值都和最后一条读入的记录一致。
根因分析
问题出在内存分配的位置错误:
初始错误写法中,用于缓存单条记录的val_t类型value数组的calloc内存分配操作写在了文件读取循环的外部,直接导致所有链表节点的vals指针都指向同一块堆内存地址。循环中每次写入新解析的文件数据时,都会直接覆盖该内存地址上存储的旧值——所有节点因为指向同一块内存,读出来的自然都是最后一次写入的内容,看起来就像历史值被“篡改”了。
修复方案
核心修复逻辑三点:
- 将value数组的
calloc内存分配操作移动到每次读取单条记录的循环内部,为每一个待插入的节点分配独立的内存空间,从根源上避免多节点共享内存的问题 - 补全内存分配失败的校验逻辑,出现分配错误时及时回滚、回收已申请的资源,避免内存泄漏
- 针对
char*字符串类型字段,配套完善节点销毁、解析失败场景下的字符串内存回收逻辑,避免野指针和内存泄漏
错误实现核心片段
// 问题代码:内存分配在循环外 int read_file(list_t *list, header_t *header, const char *file_path) { FILE *fp = fopen(file_path, "r"); if (!fp) return -1; char line_buf[2048]; // 错误点:所有循环迭代共用同一块value内存 val_t *value = (val_t*)calloc(header->field_cnt, sizeof(val_t)); if (!value) { fclose(fp); return -1; } while (fgets(line_buf, sizeof(line_buf), fp)) { // 逐字段解析行内容写入value数组 parse_line_to_val(line_buf, header, value); // 节点内vals指针直接指向全局共用的value insert_node_to_tail(list, value); } fclose(fp); return 0; }
修复后正确实现核心片段
// 修复代码:每次循环单独分配内存 int read_file(list_t *list, header_t *header, const char *file_path) { FILE *fp = fopen(file_path, "r"); if (!fp) return -1; char line_buf[2048]; while (fgets(line_buf, sizeof(line_buf), fp)) { // 修复点:为当前待插入的单条记录分配独立内存 val_t *value = (val_t*)calloc(header->field_cnt, sizeof(val_t)); if (!value) { fclose(fp); list_destroy(list, header); // 销毁已插入节点,回收所有内存 return -1; } // 逐字段解析行内容写入当前独立的value数组 if (parse_line_to_val(line_buf, header, value) != 0) { free_val_record(value, header); // 先释放记录内的字符串堆内存 free(value); continue; } // 每个节点持有独立的value内存,互不干扰 insert_node_to_tail(list, value); } fclose(fp); return 0; }
说明:修复前后的代码均保留原始C语言语法、关键字、标准库函数名,未修改原有核心结构定义与对外函数接口。
内容的提问来源于stack exchange,提问作者Joki_004
相关产品推荐
相关产品推荐

