1. freopen() 将 stdout 重定向到其他文件流
stdout 是输入到控制台中的文件流,但可以通过 freopen() 函数将其重定向到其他文件流,即输出内容不再显示在控制台,而在指定文件流(例如自定义的文件)中。例如,将 stdout 原本输出到控制台的内容输出到 output.txt 文件中。
void TestFreopen(){
// attention: Chinese cannot be written here!
freopen("output.txt", "a", stdout); // 将 stdout 文件流重定向到 output.log 文件中
puts("hello world");
fclose(stdout);
}
freopen() 函数的缺陷在于可以将 stdout 重定向到其他文件流中,但无法将 stdout 恢复至原有文件流。即可以将 stdout 由控制台输出变换到文件输出,但不能将 stdout 由文件输出恢复到控制台输出,重定向过程不可逆。原因在于 freopen() 的第一个参数为文件名,而不是文件流。
2. dup(),dup2() 将 stdout 重定向到其他文件流
为了将重定向过程可逆,即 stdout 可以重定向到其他文件流,也可从其他文件流重定向到原有的 stdout,使用 dup() 和 dup2() 函数。
dup() 函数可以复制一个文件的文件描述符,如下图所示。定义一个 fd1,将 fd0 的文件描述符赋值给 fd1。
dup2() 函数的操作对象是两个文件描述符,可将 fd1 的文件描述符指向 fd0 文件描述符指向的文件。
此外,可利用 fileno() 函数获取文件流的文件描述符。close() 函数功能为关闭文件描述符。
每个文件流对应一个文件描述符,每个文件描述符对应一个文件。所以,stdout 文件流对应一个控制台输出文件 stdout_A,其他文件流(一个的情况下)也对应一个文件 file_B。利用 freopen() 函数可将 stdout 文件流对应的文件从 stdout_A 转换为 file_B,而利用 dup2() 函数可将 stdout 对应的 file_B 文件重新转化为 stdout_A。这其中利用的便是文件描述符,修改 stdout 的文件描述符,让其重新指向 stdout_A 文件。
// dup() 函数在不同系统下的头文件名
#if defined(__APPLE__) || defined(__linux__)
# include <unistd.h>
#elif defined(_WIN32)
# include <io.h>
#endif
// 1. 是否要转化为文件流
// 2. 转化之前是否是控制台输出的文件流
void TestDup(char const* filename){
static int save_stdout_no = -1; // 定义一个文件描述符,-1 为控制台输出
if(filename){ // 将 stdout 重定向至 其他文件流
if(save_stdout_no == -1){ // 如果此时为控制台输出
// 保存 stdout 控制台输出的文件描述符
save_stdout_no = dup(fileno(stdout));
}
fflush(stdout); // 将控制台缓存全部清除
freopen(filename, "a", stdout); // 文件流之间的相互转化可借助文件名进行,stdout没有对应文件名,所以麻烦了点
}else{ // 如果文件名为 NULL,说明想恢复至原有的 stdout 文件流
if(save_stdout_no != -1){ // 不在控制台输出,需要转换为控制台输出; 否则就是在控制台输出,不需要转换
fflush(stdout);
dup2(save_stdout_no ,fileno(stdout));
close(save_stdout_no);
save_stdout_no = -1; // 在控制台输出就是 -1
}
}
}
int main ()
{
puts("1"); // 控制台
TestDup("output.log"); // 转为 output.log
puts("2");
TestDup(NULL); // 转为控制台
puts("3");
TestDup("output.log"); // 转为 output.log
puts("4");
TestDup(NULL); // 转为控制台
puts("end");
return 0;
}
程序运行结果为 1 3 end 输出在控制台,而 2 4 输出在 output.log 文件中。但 msvc 编译器下运行结果有误,mingw 编译器下运行结果无误。原因暂且未知。
此外,注意 static 的用法,用 static 定义的变量,重新定义是不影响其变量值的。
void TestStatic(){
static int a = 1;
a++;
printf("a: %d\n", a);
}
int main ()
{
TestStatic(); // 2
TestStatic(); // 3
TestStatic(); // 4
TestStatic(); // 5
TestStatic(); // 6
return 0;
}
3. 其他想法
1)不同文件的文件流互相转化,应该可以使用 freopen() 函数可以完成
2)对 stdout 不一定要使用 freopen() 函数,用文件描述符完成所有的功能
3)不同文件的文件流相互转化,应该也可以使用文件描述符完成