使用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
相关产品推荐
相关产品推荐

