stdin, stdout, stderr 是 FILE* 类型的,是流/文件指针,不是文件描述符
文件描述符:系统的概念,每一个进程内部都有一个打开文件表,文件描述符( int 类型)就是打开文件表的索引
流:是 C 语言的概念, C 语言通过流来对文件进行操作。流提供了用户缓冲区。
文件描述符和流比较
| 流/文件指针 | 文件描述符 | |
|---|---|---|
| 函数 | read(), write() |
fgets(), printf() |
| 缓冲区 | 可以有 | 一般没有 |
| 类型 | FILE* |
int |
| 例子 | stdin, stdout, stderr |
0, 1, 2 |
stdout背后的标准输出文件
向一个文件描述符写入内容(通过系统提供的 write() 函数)不需要经过缓冲
向一个流写入内容(通过 C 语言提供的 fwrite() 函数)一般要经过缓冲
可以这样理解文件描述符和流的关系:
- 流相当于在文件描述符外面包装了一层缓冲区
buffer

流缓冲的存在
由于流缓冲的存在,向流中写入的内容,并不会马上写入到文件中
以下的例子可以很好地说明这一点:(虽然 fputs("first") 先调用,但是由于缓冲区的存在,实际写入的时刻在 fputs("\n") )
write直接向标准输出文件写数据,直接到达;- 而
fputs通过流stdout的缓冲区buffer向标准输出文件写数据,等到满足条件才到达。 - 即:
stdout并不等于标准输出文件。 - 注:如果使用
printf函数,内容会马上出现在屏幕上,大概是因为printf里面调用了fflush吧。
代码如下:
int main(int argc, char **argv){fputs("first ", stdout);char stdout_filedes[256] = "last ";write(fileno(stdout), stdout_filedes, sizeof(stdout_filedes));fputs("\n", stdout); // fputs 实际生效的时候return 0;}
得到输出为:
>>> ./main.exelast first
流相关函数 & 描述符相关函数
操纵流的函数
注:流就是 FILE*
前缀为 f 的函数,这些函数都位于 stdio.h 中,是 C 标准库,有些符合 posix 标准
包括: fopen, flose, fprintf, fscanf, fputc, fputs, fgetc, fgets
操作文件描述符的函数
文件描述符就是 int 类型的
这些函数是系统提供的,分散在如 unistd.h 等文件中;不是 C 标准函数,有些符合 posix 标准
包括: read, write, close, open, fcntl, ioctl, lseek, pread, readdir, readlink, readv, select 等
流的模型示意
以stdin, stdout, stderr为例:
FIFE* stdin是一个流
通过键盘向stdin写入数据,当输入数据充满了缓冲区,或者输入了换行符之后,缓冲区中的内容会被flush到内核中,此时,stdin就是可读的。
如果使用fgets函数来读取stdin。当stdin可读之后,fgets会读取一定长度的内容或者读一行内容,到指定的内存区域。
注:所谓的键盘缓冲,实际就是stdin的缓冲。
FIFE* stdout是一个流
如果使用fputs函数向stdout输出内容。内容首先被输入到内核,然后内核将内容放入stdout的缓冲区。如果缓冲区被充满,或者进入了一个换行符,缓冲区中的内容就会被输出到stdout。
FIFE* stderr是一个流
stderr通常不设置缓冲区。
如图所示:
验证流缓冲
通过以下的例子,来说明流中的缓冲:
很好地验证了stdout有缓冲,以及缓冲的作用机制。
输出输出重定向
不进行输入输出重定向时:
对标准输入进行重定向: ./main < temp 
示意图:
得出结论:输入输出重定向不会影响 stdin, stdout, stderr 和 0, 1, 2 之间的对应关系
只会改变文件描述符到实际文件的对应关系。
打开文件表
进程的 PCB(process control block)中有一张打开文件表,文件描述符就是这张表的索引。每一个表项指向内核中的一个 file 结构体
file 结构体记录了文件的打开方式 f_flags ,当前位置 f_pos ,以及引用计数 f_count 等。其中:
- 不同进程,就算打开同一个文件,对应的也是不同的
file结构体;所以,每个进程针对同一个文件可以有不同的读写位置f_pos - 引用计数
f_count只有在fork, dup时才会增加
file 结构体还指向 file_operater 的结构体,这个结构体中全是函数指针,内核中各种文件操作函数。用户态的各种 read, write 函数,最终会调用这些函数。
如下图所示:
