7、标准输入输出重定向:freopen()函数的使用
说实话,很多C语言教材讲文件操作时,都会把freopen()放在角落里。我刚开始学的时候也觉得这函数没啥用——不就是换个文件流嘛?直到有一次做嵌入式日志系统,我才发现这玩意儿简直是救星。
今天我们就来聊聊freopen()。它能把标准输入、标准输出、标准错误流,统统重定向到文件里。说白了,就是让printf()打印到文件,让scanf()从文件读数据。
7.1 为什么需要重定向?
你想想看,在嵌入式开发中,很多时候没有显示器、没有键盘。程序跑在开发板上,printf()打印的东西你看不到。怎么办?
我个人的习惯是:把标准输出重定向到一个日志文件。这样程序运行结束后,打开文件就能看到所有输出。调试起来特别方便。
再比如,你要批量测试一组输入数据。手动敲键盘?太慢了。把数据写进文件,重定向标准输入,程序自动读取——效率翻倍。
printf()、scanf()、fgets()等函数完全不用改。
7.2 freopen()函数原型
先看函数原型,很简单:
FILE *freopen(const char *filename, const char *mode, FILE *stream);
三个参数:
- filename:要打开的文件名
- mode:打开模式,和
fopen()一样("r", "w", "a"等) - stream:要重定向的流,通常是
stdin、stdout、stderr
返回值:成功返回文件指针,失败返回NULL。
freopen()后,原来的流会被关闭。你不需要手动fclose()它。
7.3 标准输出重定向到文件
这是最常用的场景。把printf()的输出写到文件里。
#include <stdio.h>
int main() {
// 将标准输出重定向到 output.txt
if (freopen("output.txt", "w", stdout) == NULL) {
perror("freopen failed");
return 1;
}
// 这些 printf 会写入文件,而不是显示在屏幕上
printf("这是第一行日志\n");
printf("这是第二行日志\n");
printf("当前时间:2024年\n");
// 恢复标准输出到屏幕(可选)
// 注意:freopen 没有直接的"恢复"函数
// 可以用 dup() 系统调用保存原始 fd,但那是另一回事了
return 0;
}
运行后,打开output.txt,你会看到三行文本。屏幕上反而什么都没有。
printf()输出都去了文件。如果你还想在屏幕上显示一些信息,得用fprintf(stderr, ...)。标准错误流默认还是屏幕。
7.4 标准输入重定向到文件
这个场景我经常用在自动化测试上。把测试数据写进文件,程序从文件读取。
#include <stdio.h>
int main() {
int a, b;
// 将标准输入重定向到 input.txt
if (freopen("input.txt", "r", stdin) == NULL) {
perror("freopen failed");
return 1;
}
// 这些 scanf 会从文件读取数据
printf("请输入两个整数:\n"); // 这行也会被重定向到文件(如果 stdout 没改)
scanf("%d %d", &a, &b);
printf("a + b = %d\n", a + b);
return 0;
}
假设input.txt内容为:
10 20
程序运行后,scanf()直接从文件读取10和20,不需要你手动输入。
7.5 标准错误流重定向
标准错误流stderr通常用于输出错误信息。重定向它,可以把错误日志单独存到一个文件。
#include <stdio.h>
int main() {
// 将标准错误重定向到 error.log
if (freopen("error.log", "a", stderr) == NULL) {
perror("freopen failed");
return 1;
}
fprintf(stderr, "警告:内存不足\n");
fprintf(stderr, "错误:文件打开失败\n");
// printf 仍然输出到屏幕(如果 stdout 没被重定向)
printf("程序正常运行\n");
return 0;
}
这样,错误信息追加到error.log,正常输出显示在屏幕。调试时一目了然。
7.6 恢复标准流
freopen()没有提供直接的"恢复"函数。但有一个常用技巧:在重定向前,先保存原始文件描述符。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int saved_stdout_fd;
// 保存原始标准输出的文件描述符
saved_stdout_fd = dup(STDOUT_FILENO);
// 重定向到文件
if (freopen("output.txt", "w", stdout) == NULL) {
perror("freopen failed");
return 1;
}
printf("这行写入文件\n");
// 恢复标准输出
dup2(saved_stdout_fd, STDOUT_FILENO);
close(saved_stdout_fd);
printf("这行显示在屏幕\n");
return 0;
}
dup()和dup2()是系统调用,用于复制文件描述符。这里只是提一下,后面章节会详细讲。
7.7 知识体系图
下面这张图帮你理清freopen()的核心逻辑:
7.8 实际项目中的用法
我在做嵌入式设备日志系统时,经常这样用:
#include <stdio.h>
#include <time.h>
void init_log(const char *logfile) {
// 重定向 stdout 到日志文件
if (freopen(logfile, "a", stdout) == NULL) {
fprintf(stderr, "无法打开日志文件: %s\n", logfile);
return;
}
// 同时重定向 stderr
if (freopen(logfile, "a", stderr) == NULL) {
fprintf(stderr, "无法重定向 stderr\n");
return;
}
// 设置无缓冲,确保日志实时写入
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
int main() {
init_log("system.log");
printf("[INFO] 系统启动\n");
fprintf(stderr, "[ERROR] 传感器异常\n");
printf("[INFO] 系统关闭\n");
return 0;
}
这样,所有输出都追加到同一个日志文件,带时间戳的话更好追踪。
freopen()把输出重定向到文件,避免刷屏。正式发布时,再改回屏幕输出。代码逻辑完全不用动。
7.9 常见错误与避坑
- 文件打开失败: 检查路径权限。嵌入式环境下,文件系统可能只读。
- 忘记恢复流: 重定向后,所有输出都去了文件。如果后续代码需要屏幕交互,记得用
dup2()恢复。 - 缓冲区问题: 默认stdout是行缓冲的。重定向到文件后,可能变成全缓冲。用
setbuf(stdout, NULL)设为无缓冲。 - 多线程环境:
freopen()不是线程安全的。多线程程序中要加锁。
freopen(),每次打开同一个文件。结果文件描述符泄漏,程序跑着跑着就崩溃了。记住,每次freopen()都会关闭旧流,但如果你频繁调用,最好检查返回值。
7.10 总结
freopen()是个小巧但实用的函数。它让你在不修改代码的前提下,改变数据流向。无论是日志记录、自动化测试,还是嵌入式调试,它都能派上大用场。
嗯,掌握它,你的C语言工具箱里又多了一件利器。