C语言深度解析 ungetc

基础定义与陷阱

int ungetc(int c, FILE *stream);
  • 核心功能:将字符c回退到输入流stream,后续读取顺序为后进先出
  • 隐藏规则
  • 仅保证至少1次成功回退(C99标准)
  • 多次回退后读取顺序:
  • ungetc('A', fp); ungetc('B', fp); → 读取顺序为B→A
  • 文件结束符EOF处理:调用ungetc后清除流的EOF状态

实战场景

场景1:动态类型解析器

void parse_value(FILE *fp) {
    int c = fgetc(fp);
    if (isdigit(c)) {
        ungetc(c, fp);  // 回退数字字符
        int num;
        fscanf(fp, "%d", &num);
        printf("Parsed number: %d\n", num);
    } else if (c == '"') {
        parse_string(fp);  // 进入字符串解析
    } else {
        ungetc(c, fp);  // 回退未知字符
        parse_identifier(fp);
    }
}

设计亮点:通过预读+回退实现多类型解析分支


场景2:二进制流标记检测

int find_magic(FILE *fp, const char *magic) {
    int match = 1;
    long pos = ftell(fp);
    
    for (const char *p = magic; *p; ++p) {
        int c = fgetc(fp);
        if (c != *p) {
            match = 0;
            // 关键点:精准回退已读取字节
            fseek(fp, pos, SEEK_SET);
            break;
        }
    }
    return match;
}

对比优势:相比单纯使用ungetc,fseek+ftell组合更适合二进制流回溯


高级技巧:缓冲区冲突测试

char buf;
FILE *fp = fopen("test.txt", "r");

setvbuf(fp, buf, _IOFBF, sizeof(buf));  // 设置全缓冲
int c1 = fgetc(fp);                     // 触发缓冲填充
ungetc('X', fp);                        // 向缓冲区插入字符

// 验证缓冲区修改
printf("Current buffer: [");
for (int i=0; i<sizeof(buf); i++) {
    printf("%c ", (buf[i] == 0) ? '_' : buf[i]);
}
printf("]\n");

运行结果:缓冲区首字符被修改为X,展示库函数内部缓冲区操作机制


流状态劫持:动态修改输入内容

// 运行时替换输入流中的敏感词
void filter_swear_words(FILE *fp) {
    int c;
    while ((c = fgetc(fp)) != EOF) {
        if (c == 'F') {  // 检测敏感词起始字符
            ungetc('?', fp);  // 替换为无害字符
            ungetc('?', fp);  // 双写保持字符数一致
            break;
        }
        putchar(c);
    }
}

独特价值
实现「字符级输入流动态重写」,适用于实时过滤系统(如聊天室脏话过滤),相比传统先读后处理的方案节省内存。


跨平台差异警告表

行为

Linux (glibc)

Windows (MSVCRT)

最大回退次数

4 (默认缓冲大小)

1 (严格遵守C99)

回退EOF

允许

导致未定义行为

混合fseek后的有效性

失效

部分版本保留回退字符