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

混用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()是格式化读取,二者对文件指针的处理逻辑存在差异,若后续代码修改了文件操作逻辑,容易出现难以排查的指针位置错乱问题。

三、鲁棒性优化方案

  1. 严格校验首行数据:用sscanf()解析首行时,检查返回值确保只读取到合法整数,同时验证首行无多余无效字符。
  2. 统一使用fgets()+sscanf()处理所有行:避免混合IO函数的逻辑冲突,同时支持自定义格式的port_name(如需带空格,可约定用引号包裹或指定分隔符)。
  3. 限制字符读取长度:用%Ns格式符(N为结构体中port_name数组大小-1),防止缓冲区溢出。
  4. 添加边界与错误检查:读取前检查数组容量,读取每行后校验sscanf()返回值,确保数据格式正确;处理EOF和读取失败的情况,及时终止并清理资源。
  5. 校验实际读取数量:对比首行声明的端口总数与实际读取到的数量,不一致时抛出错误提示。

四、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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 03:39:25