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

C语言解析类XML文本存入汽车双向链表的代码问题排查

问题根源与修复方案

现有代码的核心错误

  • 野指针问题:声明Car *pCar后未调用malloc分配内存,直接访问成员会触发段错误
  • 解析逻辑缺失:仅处理了3个顶层起始标签,嵌套的model、车主信息、尺寸字段完全未覆盖,且块级标签(<Owners>、<Dimensions>)内部的多行内容没有对应解析逻辑
  • 类型不匹配:cylinders是int类型、尺寸字段是float类型,直接用strncpy拷贝字符串到数值类型的内存地址会导致数据完全错误
  • 链表操作错误:仅在文件读取循环结束后调用一次fnAdd,应该每解析完一个完整的<car>块就将填充完成的节点加入链表
  • _starr结构体的count字段未被利用,多值存储逻辑完全缺失

实现思路

逐行读取文件时增加状态标记,区分当前处于哪个解析块:

  1. 遇到//开头的行直接跳过
  2. 遇到<car>标签时,为新的Car节点分配内存,初始化所有计数字段为0、数值字段为0、前后指针为NULL
  3. 遇到块级起始标签(<Owners>、<Dimensions>)时,切换对应解析状态,直到遇到对应的闭合标签再退出状态
  4. 标签值提取简化为:定位行内第一个>和后续第一个<的位置,两个符号中间的内容就是标签对应的值,不需要反复调用strtok
  5. 多model、多车主存储:解析到对应标签时,按照_starr内的count值作为索引,将值拷贝到string数组对应位置,拷贝完成后count自增
  6. 数值字段处理:提取到的字符串值用atoi转int存气缸数,用atof转float存长宽尺寸
  7. 遇到</car>标签时,将填充完成的Car节点加入双向链表
  8. 文件读取完成后记得遍历链表释放内存,避免内存泄漏

修正后完整代码

#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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:24:22