混用fgets()与fscanf()读取同一文件的风险及代码优化问询
问题解析与解决方案
一、当前代码能运行的核心原因
fgets()读取首行后,文件指针停留在首行的换行符位置;而fscanf()的格式符(如%s、%u)会自动跳过前置的空白字符(包括换行、空格、制表符等),因此后续调用fscanf()时会直接跳过首行残留的换行符,读取下一行的port_name和port_number,这就是代码意外运行成功的关键。
二、潜在风险
- 首行格式校验缺失:若首行包含非数字字符(如
"5 abc"),转成整数时会截断,但文件指针会停留在无效字符处,后续fscanf()会读取错误内容;若首行是负数或0,会导致后续读取逻辑完全混乱。 port_name含空格时失效:fscanf()的%s会在空格处停止读取,若port_name是带空格的字符串(如"eth0 main"),会只读取"eth0",剩余部分会被误判为port_number,导致数据完全错误。- 缓冲区溢出风险:若
port_name长度超过结构体中字符数组的定义大小,fscanf()的%s会直接越界写入,触发内存溢出等未定义行为。 - 文件行数不足时的越界访问:若文件实际行数少于首行声明的端口总数,
fscanf()会持续读取直到EOF,给结构体数组填充垃圾值,甚至导致数组越界访问。 - 混合IO函数的隐性问题:
fgets()是行缓冲读取,fscanf()是格式化读取,二者对文件指针的处理逻辑存在差异,若后续代码修改了文件操作逻辑,容易出现难以排查的指针位置错乱问题。
三、鲁棒性优化方案
- 严格校验首行数据:用
sscanf()解析首行时,检查返回值确保只读取到合法整数,同时验证首行无多余无效字符。 - 统一使用
fgets()+sscanf()处理所有行:避免混合IO函数的逻辑冲突,同时支持自定义格式的port_name(如需带空格,可约定用引号包裹或指定分隔符)。 - 限制字符读取长度:用
%Ns格式符(N为结构体中port_name数组大小-1),防止缓冲区溢出。 - 添加边界与错误检查:读取前检查数组容量,读取每行后校验
sscanf()返回值,确保数据格式正确;处理EOF和读取失败的情况,及时终止并清理资源。 - 校验实际读取数量:对比首行声明的端口总数与实际读取到的数量,不一致时抛出错误提示。
四、fgets()+sscanf()实现示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_PORT_NAME_LEN 32 typedef struct { char port_name[MAX_PORT_NAME_LEN]; uint32_t port_number; } PortEntry; int main() { FILE *fp = fopen("port_table.txt", "r"); if (!fp) { perror("Failed to open file"); return EXIT_FAILURE; } char buffer[128]; int port_count = 0; // 读取并校验首行端口总数 if (!fgets(buffer, sizeof(buffer), fp)) { perror("Failed to read port count"); fclose(fp); return EXIT_FAILURE; } if (sscanf(buffer, "%d", &port_count) != 1 || port_count <= 0) { fprintf(stderr, "Invalid port count format\n"); fclose(fp); return EXIT_FAILURE; } // 动态分配内存(也可使用静态数组,动态更灵活) PortEntry *transfered_port_table = malloc(port_count * sizeof(PortEntry)); if (!transfered_port_table) { perror("Failed to allocate memory"); fclose(fp); return EXIT_FAILURE; } int read_count = 0; while (read_count < port_count && fgets(buffer, sizeof(buffer), fp)) { // 去除换行符(若存在) size_t len = strlen(buffer); if (len > 0 && buffer[len-1] == '\n') { buffer[len-1] = '\0'; } // 解析每行数据,限制port_name长度防止溢出 if (sscanf(buffer, "%31s %u", transfered_port_table[read_count].port_name, &transfered_port_table[read_count].port_number) == 2) { read_count++; } else { fprintf(stderr, "Invalid data format at line %d\n", read_count + 2); break; } } // 校验实际读取数量 if (read_count != port_count) { fprintf(stderr, "Expected %d ports, only read %d\n", port_count, read_count); } // 后续数据处理逻辑... // 清理资源 free(transfered_port_table); fclose(fp); return EXIT_SUCCESS; }
内容的提问来源于stack exchange,提问作者DavidKanes
相关产品推荐
相关产品推荐

