C语言解析类XML文本存入汽车双向链表的代码问题排查
问题根源与修复方案
现有代码的核心错误
- 野指针问题:声明
Car *pCar后未调用malloc分配内存,直接访问成员会触发段错误 - 解析逻辑缺失:仅处理了3个顶层起始标签,嵌套的model、车主信息、尺寸字段完全未覆盖,且块级标签(
<Owners>、<Dimensions>)内部的多行内容没有对应解析逻辑 - 类型不匹配:
cylinders是int类型、尺寸字段是float类型,直接用strncpy拷贝字符串到数值类型的内存地址会导致数据完全错误 - 链表操作错误:仅在文件读取循环结束后调用一次
fnAdd,应该每解析完一个完整的<car>块就将填充完成的节点加入链表 _starr结构体的count字段未被利用,多值存储逻辑完全缺失
实现思路
逐行读取文件时增加状态标记,区分当前处于哪个解析块:
- 遇到
//开头的行直接跳过 - 遇到
<car>标签时,为新的Car节点分配内存,初始化所有计数字段为0、数值字段为0、前后指针为NULL - 遇到块级起始标签(
<Owners>、<Dimensions>)时,切换对应解析状态,直到遇到对应的闭合标签再退出状态 - 标签值提取简化为:定位行内第一个
>和后续第一个<的位置,两个符号中间的内容就是标签对应的值,不需要反复调用strtok - 多model、多车主存储:解析到对应标签时,按照
_starr内的count值作为索引,将值拷贝到string数组对应位置,拷贝完成后count自增 - 数值字段处理:提取到的字符串值用
atoi转int存气缸数,用atof转float存长宽尺寸 - 遇到
</car>标签时,将填充完成的Car节点加入双向链表 - 文件读取完成后记得遍历链表释放内存,避免内存泄漏
修正后完整代码
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #define MAX_LEN 256 #define MAX_STR_ENTRIES 10 #define MAX_STR_LEN 30 // 解析状态枚举 typedef enum { STATE_NORMAL, STATE_IN_OWNERS, STATE_IN_DIMENSIONS } ParseState; struct _starr { int count; char string[MAX_STR_ENTRIES][MAX_STR_LEN]; }; struct _Dim { float length; float width; }; typedef struct _Car { int cylinders; struct _starr Owners; struct _Dim Dimensions; struct _starr models; struct _Car *prev; struct _Car *next; } Car; typedef struct _List { int num; Car *head; Car *tail; } CarList; // 去除字符串首尾空白 char* trim(char *str) { while(isspace((unsigned char)*str)) str++; if(*str == 0) return str; char *end = str + strlen(str) - 1; while(end > str && isspace((unsigned char)*end)) end--; end[1] = '\0'; return str; } // 从标签行提取值,存到out缓冲区,成功返回1,失败返回0 int extract_tag_value(const char *line, char *out, size_t out_len) { const char *start = strchr(line, '>'); if (!start) return 0; start++; const char *end = strchr(start, '<'); if (!end) return 0; size_t val_len = end - start; if (val_len >= out_len) val_len = out_len - 1; strncpy(out, start, val_len); out[val_len] = '\0'; // 去除首尾空白 char *trimmed = trim(out); if (trimmed != out) memmove(out, trimmed, strlen(trimmed)+1); return 1; } void fnAdd (CarList *list, Car *pCar) { if(list->head == NULL) { pCar->prev = NULL; pCar->next = NULL; list->head = pCar; list->tail = pCar; } else { pCar->prev = list->tail; pCar->next = NULL; list->tail->next = pCar; list->tail = pCar; } list->num++; } // 测试用:遍历链表打印所有车辆信息 void print_list(const CarList *list) { Car *cur = list->head; int idx = 1; while(cur) { printf("=== Car %d ===\n", idx++); printf("Cylinders: %d\n", cur->cylinders); printf("Models(%d): ", cur->models.count); for(int i=0; i<cur->models.count; i++) { printf("%s ", cur->models.string[i]); } printf("\nOwners(%d): ", cur->Owners.count); for(int i=0; i<cur->Owners.count; i++) { printf("%s ", cur->Owners.string[i]); } printf("\nDimensions: length=%.1f, width=%.1f\n", cur->Dimensions.length, cur->Dimensions.width); cur = cur->next; } } // 释放链表内存 void free_list(CarList *list) { Car *cur = list->head; while(cur) { Car *tmp = cur; cur = cur->next; free(tmp); } list->head = NULL; list->tail = NULL; list->num = 0; } int main(void) { CarList List; List.num = 0; List.head = NULL; List.tail = NULL; ParseState state = STATE_NORMAL; Car *pCar = NULL; FILE *fp = fopen("test1.txt", "r"); if (fp == NULL) { perror("Failed to open file"); return 1; } char buffer[MAX_LEN]; char val_buf[MAX_LEN]; while (fgets(buffer, MAX_LEN, fp)) { // 跳过//开头的注释行,先跳过行首空白 char *line_start = buffer; while(isspace((unsigned char)*line_start)) line_start++; if (line_start[0] == '/' && line_start[1] == '/') continue; // 去掉换行符 buffer[strcspn(buffer, "\r\n")] = 0; // 处理闭合标签 if (strstr(line_start, "</car>")) { if (pCar) { fnAdd(&List, pCar); pCar = NULL; } state = STATE_NORMAL; continue; } if (strstr(line_start, "</Owners>")) { state = STATE_NORMAL; continue; } if (strstr(line_start, "</Dimensions>")) { state = STATE_NORMAL; continue; } // 处理起始块标签 if (strstr(line_start, "<car>")) { pCar = (Car*)calloc(1, sizeof(Car)); // calloc自动初始化所有字段为0 if (!pCar) { perror("malloc failed"); fclose(fp); free_list(&List); return 1; } state = STATE_NORMAL; continue; } if (strstr(line_start, "<Owners>")) { state = STATE_IN_OWNERS; continue; } if (strstr(line_start, "<Dimensions>")) { state = STATE_IN_DIMENSIONS; continue; } // 没有当前节点说明标签在car块外,跳过 if (!pCar) continue; // 按当前状态处理内容标签 switch(state) { case STATE_NORMAL: if (strstr(line_start, "<model>") && extract_tag_value(line_start, val_buf, sizeof(val_buf))) { if (pCar->models.count < MAX_STR_ENTRIES) { strncpy(pCar->models.string[pCar->models.count], val_buf, MAX_STR_LEN-1); pCar->models.string[pCar->models.count][MAX_STR_LEN-1] = '\0'; pCar->models.count++; } } else if (strstr(line_start, "<cylinders>") && extract_tag_value(line_start, val_buf, sizeof(val_buf))) { pCar->cylinders = atoi(val_buf); } break; case STATE_IN_OWNERS: // 跳过NumberofOwners标签,实际数量直接用count统计,要存也可以转int存 if (strstr(line_start, "<NumberofOwners>")) break; if (extract_tag_value(line_start, val_buf, sizeof(val_buf))) { if (pCar->Owners.count < MAX_STR_ENTRIES) { strncpy(pCar->Owners.string[pCar->Owners.count], val_buf, MAX_STR_LEN-1); pCar->Owners.string[pCar->Owners.count][MAX_STR_LEN-1] = '\0'; pCar->Owners.count++; } } break; case STATE_IN_DIMENSIONS: if (strstr(line_start, "<Length>") && extract_tag_value(line_start, val_buf, sizeof(val_buf))) { pCar->Dimensions.length = atof(val_buf); } else if (strstr(line_start, "<Width>") && extract_tag_value(line_start, val_buf, sizeof(val_buf))) { pCar->Dimensions.width = atof(val_buf); } break; } } fclose(fp); // 测试打印 print_list(&List); // 释放内存 free_list(&List); return 0; }
关键逻辑说明
trim函数用来去除标签值首尾的空格,比如示例里<FirstOwner> FirstOwner </FirstOwner>两边的空格会被清掉extract_tag_value是通用的标签值提取函数,比原来写的两次strtok循环更简洁稳定,自动处理截断避免缓冲区溢出- 用
calloc分配Car节点内存,自动把所有字段初始化为0,不需要手动memset - 多值存储直接用
_starr的count做数组下标,存完自增,天然支持多个model、多个车主的存储,不需要提前读NumberofOwners字段,count值就是实际车主数量 - 尺寸字段提取到字符串后用
atof转成float类型,直接存到结构体对应字段,不需要用strncpy拷贝 - 所有字符串拷贝都预留了
\0的位置,避免字符串不结束导致的乱码问题
内容的提问来源于stack exchange,提问作者nickpts
相关产品推荐
相关产品推荐

