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

使用C语言读取CSV文件时最后一行触发Segmentation Fault问题求助

CSV读取最后一行触发段错误的问题分析与修复

我有一个包含10000行数据的CSV文件,调试代码时发现读取最后一行会触发Segmentation Fault (core dumped)错误,删除该行后错误消失。我的目标是将驾驶员信息存储到链表中,相关代码如下:

struct drivers {
    char *id;
    char *name;
    char *birth_day; 
    char *gender; 
    char *car_class;
    char *license_plate;
    char *city;
    char *account_creation;
    char *account_status; 
};

typedef struct drivers *Drivers;

Drivers initDriver (char *id, char *name, char *birth_day, char *gender, char *car_class, char *license_plate, char *city, char *account_creation, char *account_status) {
    Drivers driver = malloc(sizeof(struct drivers));
    driver -> id = strdup(id);
    driver -> name = strdup(name);
    driver -> birth_day = strdup(birth_day);
    driver -> gender = strdup(gender);
    driver -> car_class = strdup(car_class);
    driver -> license_plate = strdup(license_plate);
    driver -> city = strdup(city);
    driver -> account_creation = strdup(account_creation);
    driver -> account_status = strdup(account_status);
    printf("%s;%s;%s;%s;%s;%s;%s;%s;%s", id, name, birth_day, gender, car_class, license_plate, city, account_creation, account_status);
    return driver;
}

Drivers initDriverFromLine (char *linha) {
    char *id = (strsep(&linha,";"));
    char *name = (strsep(&linha,";"));
    char *birth_day = (strsep(&linha,";"));
    char *gender = (strsep(&linha,";"));
    char *car_class = (strsep(&linha,";"));
    char *license_plate = (strsep(&linha,";"));
    char *city = (strsep(&linha,";"));
    char *account_creation = (strsep(&linha,";"));
    char *account_status = (strsep(&linha,";"));
    
    return initDriver(id, name, birth_day, gender, car_class, license_plate, city, account_creation, account_status);
}

int main () {
    FILE* Modulo1 = fopen("drivers.csv","r");
    if (Modulo1 == NULL) printf("Erro ao abrir o ficheiro.\n");

    char linha[1024];
    while (fgets(linha, sizeof(linha), Modulo1) != NULL) {
        initDriverFromLine(fgets(linha, sizeof(linha), Modulo1));
    }
    fclose(Modulo1);

    return 0;
}

问题根源分析

  • 重复调用fgets导致空指针传入:main函数的while循环中,循环条件先调用fgets读一行,循环体内又调用fgets再读一行。当文件行数为奇数时,最后一次循环体内的fgets会返回NULL,将NULL传给initDriverFromLine后,strsep和strdup操作空指针直接触发段错误。
  • 未检查内存分配返回值:malloc和strdup在内存不足时会返回NULL,后续访问结构体成员会引发未定义行为。
  • CSV行处理不严谨:如果某一行字段数量不足9个,strsep会返回NULL,传给strdup同样会导致段错误;此外fgets会读取换行符,未处理的话会导致存储的字段包含多余换行符。
  • 文件打开失败后未终止程序:fopen失败后继续执行后续代码,会对NULL文件指针进行操作,引发未定义行为。

修复后的代码

1. 修复main函数的读取逻辑

int main () {
    FILE* Modulo1 = fopen("drivers.csv","r");
    if (Modulo1 == NULL) {
        printf("Erro ao abrir o ficheiro.\n");
        return 1; // 打开失败直接退出,避免后续错误操作
    }

    char linha[1024];
    // 仅在循环条件中读取一行,循环体内处理该行
    while (fgets(linha, sizeof(linha), Modulo1) != NULL) {
        // 去掉换行符,避免字段包含多余的换行
        linha[strcspn(linha, "\n")] = '\0';
        initDriverFromLine(linha);
    }
    fclose(Modulo1);

    return 0;
}

2. 完善initDriver的空指针检查与内存管理

Drivers initDriver (char *id, char *name, char *birth_day, char *gender, char *car_class, char *license_plate, char *city, char *account_creation, char *account_status) {
    // 先检查所有输入参数是否有效
    if (!id || !name || !birth_day || !gender || !car_class || !license_plate || !city || !account_creation || !account_status) {
        fprintf(stderr, "Invalid driver data: missing fields\n");
        return NULL;
    }

    Drivers driver = malloc(sizeof(struct drivers));
    if (!driver) {
        perror("Failed to allocate memory for driver");
        return NULL;
    }

    // 逐个检查strdup的返回值,若某一步失败则释放已分配的内存
    if (!(driver->id = strdup(id)) ||
        !(driver->name = strdup(name)) ||
        !(driver->birth_day = strdup(birth_day)) ||
        !(driver->gender = strdup(gender)) ||
        !(driver->car_class = strdup(car_class)) ||
        !(driver->license_plate = strdup(license_plate)) ||
        !(driver->city = strdup(city)) ||
        !(driver->account_creation = strdup(account_creation)) ||
        !(driver->account_status = strdup(account_status))) {
        // 释放已成功分配的字段内存
        free(driver->id);
        free(driver->name);
        free(driver->birth_day);
        free(driver->gender);
        free(driver->car_class);
        free(driver->license_plate);
        free(driver->city);
        free(driver->account_creation);
        free(driver); // 释放结构体本身
        perror("Failed to duplicate string");
        return NULL;
    }

    printf("%s;%s;%s;%s;%s;%s;%s;%s;%s\n", id, name, birth_day, gender, car_class, license_plate, city, account_creation, account_status);
    return driver;
}

3. 优化initDriverFromLine的字符串处理

Drivers initDriverFromLine (char *linha) {
    if (!linha) {
        fprintf(stderr, "Null line passed to initDriverFromLine\n");
        return NULL;
    }

    // 复制原字符串,避免修改输入的linha数组
    char *linha_copy = strdup(linha);
    if (!linha_copy) {
        perror("Failed to duplicate line");
        return NULL;
    }

    char *token = linha_copy;
    char *id = strsep(&token,";");
    char *name = strsep(&token,";");
    char *birth_day = strsep(&token,";");
    char *gender = strsep(&token,";");
    char *car_class = strsep(&token,";");
    char *license_plate = strsep(&token,";");
    char *city = strsep(&token,";");
    char *account_creation = strsep(&token,";");
    char *account_status = strsep(&token,";");

    Drivers driver = initDriver(id, name, birth_day, gender, car_class, license_plate, city, account_creation, account_status);
    free(linha_copy); // 释放复制的字符串,避免内存泄漏
    return driver;
}

额外注意事项

  • 链表存储实现:当前代码仅创建了单个driver节点,需要额外实现链表的头节点和addDriverToList函数,将创建的节点添加到链表中。
  • 内存泄漏处理:程序退出前需要遍历链表,逐个释放每个driver节点的所有字段内存和节点本身的内存,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 10:35:24