标准输入流、输出流、错误流在操作系统与C语言中的表达

1. 操作系统层面(Linux/Unix)

在操作系统层面,标准输入、标准输出和标准错误流通过文件描述符(File Descriptor) 来标识:

流类型 文件描述符 (fd) 默认设备
标准输入 0 通常是终端/键盘
标准输出 1 通常是终端/屏幕
标准错误 2 通常是终端/屏幕

这些文件描述符本质上是进程打开的文件句柄,可以指向终端设备、普通文件或管道。

2. C语言层面

在C语言中,标准库 <stdio.h> 提供了三个预定义的 FILE* 指针:

#include <stdio.h>

// 三种标准流指针
stdin   // 标准输入流
stdout  // 标准输出流
stderr  // 标准错误流

对应关系

C语言流 文件描述符 说明
stdin 0 fgets()、scanf() 从此读取
stdout 1 printf()、puts() 往此写入(有缓冲)
stderr 2 fprintf(stderr, …) 往此写入(无缓冲)

示例代码

#include <stdio.h>
#include <unistd.h>  // for close()

int main() {
    // C语言方式使用标准流
    fprintf(stdout, "正常输出\n");
    fprintf(stderr, "错误信息\n");

    // 获取底层文件描述符
    int fd_in  = fileno(stdin);   // 0
    int fd_out = fileno(stdout);  // 1
    int fd_err = fileno(stderr);  // 2

    // 直接使用系统调用写入
    write(fd_out, "hello\n", 6);

    return 0;
}

3. stdout 与 stderr 的关键区别

特性 stdout stderr
缓冲 有缓冲(行缓冲或全缓冲) 无缓冲(立即写出)
用途 程序正常输出 错误/诊断信息
重定向 可单独重定向 可单独重定向

重定向示例

# 只重定向 stdout 到文件
./prog > output.txt

# 只重定向 stderr 到文件
./prog 2> error.log

# 将 stdout 和 stderr 都重定向到同一文件
./prog > all.txt 2>&1

4. 重定向的实现原理

./prog > output.txt 这个重定向是如何实现的?

这是通过 shell 在调用程序前修改文件描述符 实现的。核心机制如下:

  1. > 是 shell 的语法,不是程序本身的行为
  2. shell 会:
    • 打开目标文件
    • 把文件的文件描述符设为 1(标准输出)
    • 再执行你的程序
  3. 程序看到的 fd 1 已经不是终端,而是文件了

用系统调用还原 ./prog > output.txt

// shell 实际做的事(简化版)
pid_t pid = fork();
if (pid == 0) {
    // 子进程:执行重定向
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    dup2(fd, 1);  // 让 1 指向 fd
    close(fd);
    
    // 执行目标程序
    execvp("./prog", argv);
}

dup2(fd, 1) 的作用是:让文件描述符 1 和 fd 指向同一个文件。之后程序里所有的 printf()write(1, ...) 都写到文件里了。

验证实验

// test.c
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("hello\n");           // 走 stdout (fd 1)
    write(1, "world\n", 6);      // 直接写 fd 1
    return 0;
}
$ gcc test.c -o test
$ ./test              # 输出到屏幕
$ ./test > out.txt    # 输出到文件
$ cat out.txt         # 输出:hello\nworld\n

程序代码完全没变,变的只是 shell 在 exec 前把 fd 1 换成了文件。

5. 重定向的恢复问题

dup2(fd, 1) 后,何时重新把 1 指回标准输出流?

不会自动指回来——重定向的影响会持续到这个进程结束。

dup2(fd, 1) 之后,这个进程的 fd 1 就永远指向文件了,直到:

  1. 进程退出(所有文件描述符自动关闭)
  2. 进程自己再用 dup2() 改回去

shell 为什么能"恢复"?

因为 shell 是在子进程中做重定向:

+------------------+
| 父 shell 进程    |
| fd 1 = 终端      | ← 从来不会被改变
+------------------+
        |
     fork()
        |
        v
+------------------+
| 子进程           |
| fd 1 = 终端      | ← 刚 fork 时继承自父进程
| dup2(fd, 1)      |
| fd 1 = 文件      | ← 只有子进程被改了
| exec("./prog")   |
+------------------+
        |
      退出
        v
    子进程销毁,fd 全部关闭

父 shell 的 fd 1 一直是终端,不受影响。

在程序内部恢复的方法

如果想在程序内部恢复,可以在重定向前先保存原来的 fd 1:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    // 保存原始标准输出
    int saved_stdout = dup(1);  // 复制 fd 1,得到比如 fd 3

    // 重定向到文件
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    dup2(fd, 1);
    close(fd);

    // ... 现在 printf 都写到文件 ...
    printf("写入文件\n");

    // 恢复标准输出
    dup2(saved_stdout, 1);  // 把 fd 1 改回原来的终端
    close(saved_stdout);

    // ... 现在 printf 又写到屏幕了 ...
    printf("回到屏幕\n");

    return 0;
}

6. 总结

场景 何时恢复
shell 重定向 (./prog > out.txt) 子进程退出,父进程从未受影响
程序内自己 dup2 重定向 不会自动恢复,需要手动 dup2(saved_fd, 1)

通过理解文件描述符、标准流和重定向机制,可以更好地掌握Linux/Unix系统编程和C语言中I/O操作的本质。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐