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

使用fscanf后ftell返回负值致程序异常的原因排查

问题根源:文本模式下格式化输入与文件定位的冲突

你的代码出现ftell返回负值(-7)的核心原因是在文本模式下混合使用格式化输入函数(fscanf)和文件定位函数(ftell/fseek),导致文件位置指示器的状态不可靠,进而引发ftell调用失败。

具体原因拆解:

  1. 文本模式的特殊性:当你用"r"打开文件时,处于文本模式。此时系统会自动转换换行符(比如Windows下的\r\n会被转为单个\n),ftell返回的不是实际字节偏移量,而是一个仅用于fseek恢复位置的"令牌",这个值本身没有实际数值意义。
  2. 格式化输入的缓冲行为:fscanf("%d")会跳过前导空白(空格、换行、制表符),读取数字后停在第一个非数字字符处。但fscanf依赖stdio缓冲区,操作后输入流的位置可能处于缓冲区内部的不确定状态,此时调用ftell的行为是未定义的——不同实现可能返回错误值(比如你看到的-7),而非有效位置。

解决方案:两种可行修改方式

方案1:改用逐行读取+字符串解析(推荐,文本模式下更可靠)

避免直接在文件流上混合格式化输入和定位,先读取整行内容,再用sscanf解析数字,这样能精准控制文件位置:

#include <stdio.h>
#include <string.h>

int main() {
    char letter;
    int num;
    char line[256];
    FILE *fp;

    if ((fp = fopen("chance.txt", "r")) == NULL) {
        printf("File corrupted");
        return 1;
    }

    // 逐行读取文件内容
    while (fgets(line, sizeof(line), fp) != NULL) {
        char *ptr = line;
        // 解析当前行的所有数字
        while (sscanf(ptr, "%d", &num) == 1) {
            long current_pos = ftell(fp);
            // 跳转到指定索引位置
            if (fseek(fp, num, SEEK_SET) != 0) {
                printf("Seek failed\n");
                fclose(fp);
                return 1;
            }
            letter = fgetc(fp);
            printf("%c", letter);
            // 跳回当前行的下一行起始位置
            fseek(fp, current_pos, SEEK_SET);
            // 移动指针到下一个数字的起始位置
            while (*ptr != '\0' && (*ptr < '0' || *ptr > '9')) ptr++;
            while (*ptr != '\0' && (*ptr >= '0' && *ptr <= '9')) ptr++;
        }
    }

    fclose(fp);
    return 0;
}

方案2:二进制模式打开文件

如果不需要处理跨平台换行符的转换,用"rb"二进制模式打开文件,此时ftell返回实际字节偏移量,fseek和ftell的行为完全确定,不会出现位置混乱:

#include <stdio.h>

int main() {
    char letter;
    int num;
    long where;
    FILE *fp;

    // 二进制模式打开文件
    if ((fp = fopen("chance.txt", "rb")) == NULL) {
        printf("File corrupted");
        return 1;
    }

    while (fscanf(fp, "%d", &num) == 1) {
        where = ftell(fp);
        if (fseek(fp, num, SEEK_SET) != 0) {
            printf("Seek failed\n");
            fclose(fp);
            return 1;
        }
        letter = fgetc(fp);
        printf("%c", letter);
        fseek(fp, where, SEEK_SET);
    }

    fclose(fp);
    return 0;
}

注意事项:

  • 无论哪种方案,都应该检查fseek的返回值(返回0表示成功,非0表示失败),避免因无效索引导致程序崩溃。
  • 文本模式下,文件的"索引"是指逻辑字符数(换行符算1个),而二进制模式下是实际字节数,需根据你的需求选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:15:08