CSV文件最后一行解析异常问题排查与修复咨询
解析CSV时循环重复执行的问题分析与修复
问题背景
CSV文件内容
name,birthdate,phone Bob smith,1111-11-11,222-333-3333 Mary brown,2222-22-22,333-444-4444 Billy Bob,2001-01-11,222-333-4444
原解析代码
int loadCSV(char *filename) { FILE *x = fopen(filename, "rt"); if (x == NULL) exit(1); char record[1000]; char field1[100], field2[100], field3[100]; fgets(record, 999, x); fgets(record, 999, x); while (!feof(x)) { int rec = 0; int i; for (i = 0; record[rec] != '\0' && i < 999 && record[rec] != ','; i++, rec++) { field1[i] = record[rec]; } field1[i] = '\0'; rec++; for (i = 0; record[rec] != '\0' && i < 999 && record[rec] != ','; i++, rec++) { field2[i] = record[rec]; } field2[i] = '\0'; rec++; for (i = 0; record[rec] != '\0' && i < 999 && record[rec] != ',' && record[rec] != '\n' ; i++, rec++) { field3[i] = record[rec]; } field3[i] = '\0'; printf("%s %s %s\n", field1, field2, field3); fgets(record, 999, x); } return 0; }
期望输出
Bob smith,1111-11-11,222-333-3333 Mary brown,2222-22-22,1-333-444-4444 Billy Bob,2001-01-11,222-333-4444
实际输出
Bob smith, 1111-11-11, 222-333-3333 Mary brown,2222-22-22,1-333-444-4444 Billy Bob,2001-01-11,222-333-4444 Billy Bob
最小复现代码
int loadCSV(char *filename) { char record[1000]; char field1[100], field2[100], field3[100]; FILE *x = fopen(filename, "rt"); if (x == NULL) exit(1); fgets(record, 999, x); fgets(record, 999, x); while (!feof(x)) { int rec = 0; int i; for (i = 0; record[rec] != '\0' && i < 999 && record[rec] != ','; i++, rec++) { field1[i] = record[rec]; } field1[i] = '\0'; rec++; printf("%s %s %s\n", field1, field2, field3); fgets(record, 999, x); } }
问题原因
while (!feof(x))的逻辑缺陷:
feof函数只有在尝试读取并越过文件末尾后才会返回真。当读取到最后一行后,执行fgets(record, 999, x)会失败,但此时feof(x)仍为假,循环会继续执行一次。此时record中保留的是上一次读取的内容(Billy Bob那一行),所以会重复解析并输出field1的内容。最小复现中的字段未完整解析:
最小复现代码只完成了field1的解析,field2和field3未被赋值,属于未初始化的内存,打印时会输出垃圾值;同时因为上述feof的问题,最后一次循环只会输出上一次的field1内容。原代码的其他潜在问题:
- 字段复制时
i < 999的限制不合理:field数组长度仅为100,应该限制i < 99,避免数组溢出。 - 直接
rec++未做边界检查:如果某行末尾没有逗号,rec可能越界访问record数组。
- 字段复制时
修复建议
1. 替换循环条件,用fgets的返回值判断
把while (!feof(x))改为while (fgets(record, 999, x) != NULL),这样只有当成功读取到一行时才进入循环,从根源避免最后一次无效循环。
2. 正确跳过表头
原代码用两次fgets跳表头,若表头行长度超过998会导致读取不完整,建议改为:
// 跳过表头 if (fgets(record, 999, x) == NULL) { fclose(x); return 1; }
3. 完善字段解析的边界控制
- 限制字段复制时的
i不超过字段数组的最大可用长度(比如i < 99,因为数组长度100,留一个位置给终止符'\0')。 - 解析时检查
rec是否超出record的范围,避免越界。
4. 初始化字段数组
避免未初始化的垃圾值被打印,在定义字段数组时初始化:
char field1[100] = "", field2[100] = "", field3[100] = "";
修复后的完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> int loadCSV(char *filename) { FILE *x = fopen(filename, "rt"); if (x == NULL) { perror("Failed to open file"); exit(EXIT_FAILURE); } char record[1000]; char field1[100] = "", field2[100] = "", field3[100] = ""; // 跳过表头 if (fgets(record, 999, x) == NULL) { fclose(x); return 1; } // 用fgets返回值作为循环条件 while (fgets(record, 999, x) != NULL) { int rec = 0; int i; // 解析field1 for (i = 0; record[rec] != '\0' && i < 99 && record[rec] != ','; i++, rec++) { field1[i] = record[rec]; } field1[i] = '\0'; // 跳过逗号,若已到行尾则停止 if (record[rec] == ',') rec++; // 解析field2 for (i = 0; record[rec] != '\0' && i < 99 && record[rec] != ','; i++, rec++) { field2[i] = record[rec]; } field2[i] = '\0'; if (record[rec] == ',') rec++; // 解析field3,处理换行符 for (i = 0; record[rec] != '\0' && i < 99 && record[rec] != '\n'; i++, rec++) { field3[i] = record[rec]; } field3[i] = '\0'; printf("%s,%s,%s\n\n", field1, field2, field3); } fclose(x); return 0; }
内容的提问来源于stack exchange,提问作者Blue Apricot
相关产品推荐
相关产品推荐

