标准I/O

Table of Contents

1 stdin,stdout和stderr

当Linux新建一个进程时,会自动创建3个文件描述符0、1、2,分别对应标准输入、标准输出和标准错误输出。C库中与文件描述符对应的是文件指针,分别是stdin,stdout和stderr。stdin是不可写的,stdout是不可读的,stderr不仅不可读,而且没有缓存。

2 I/O缓存

C库的I/O接口对文件I/O接口进行了封装,为了提高性能,引入了缓存机制。有三种缓存机制:全缓存、行缓存和无缓存。

  • 全缓存 一般用于访问真正的磁盘文件。C库为文件访问申请一块内存,只有当文件内容将缓存填满或折行flush时才将缓存内容填入内核。
  • 行缓存 一般用于访问终端。当遇到一个换行符是引发真正的I/O操作。C库的行缓存是有固定大小的,因此当缓存已满时,即使没有换行符也会引发I/O操作。
  • 无缓存 C库不作任何缓存。

C库提供了接口用于修改默认的缓存行为。

#include <stdio.h>

void setbuf(FILE *stream, char *buf);
void setbuffer(FILE *stream, char *buf, size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

C的缓存会引发一些有趣的事情,以一个小程序为例.

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

int main() {
  printf(``Hello '')
  if (0 ==fork()) {
    printf(``child\n'');
    retur
  }
  printf(``parent\n'');
  return 0;
}

它的输出结果是

Hello parent
Hello child

或者两行上下相反的内容。这是子进程在缓存交给内核之前复制了父进程的缓存引起的。

3 fopen和open标志为位比

C库的fopen用于打开文件,内部实现必然要使用open系统调用。fopen和open的标志位对应如下。

\begin{center} \begin{tabular}{c|l|p{5cm}} \hline fopen标志位 & open标志位 & 用途\\ \hline \hline r & O\_RDONLY & 以只读方式打开文件,读写都共享同一个文件偏移,不要同时读写,除非你知道将要发生什么 \\ \hline r+ & O\_RDWR & 以读写方式打开文件 \\ \hline w & O\_WRONLY | O\_CREATE | O\_TRUNC & 以写方式打开文件;当文件不存在则创建 \\ \hline a & O\_WRONLY | O\_APPEND | O\_CREATE & 以追加写方式打开文件,当文件不存在则创建 \\ \hline a+ & O\_RDWR | O\_APPEND | O\_CREATE & 以追加读写方式打开文件,当文件不存在则创建 \\ \hline c & --- & 该文件流在I/O操作时不可被取消 \\ \hline e & O\_CLOEXEC & 当进程执行exec时,该文件流自动关闭 \\ \hline m & --- & 该文件流通过mmap打开或访问,只支持读取操作\\ \hline x & O\_EXCL & 在创建文件时,如果文件已经存在,fopen返回失败而不是打开这个文件 \\ \hline b & --- & 表示打开的文件是二进制流而不是文本流。这个标志目前是没用的\\ \hline \end{tabular} \end{center}

4 文件流和文件描述符转换

C库提供了FILE*和文件描述符的转换API。

#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);

无论使用那种方式转换,都要使用fclose释放资源。

5 ferror和clearerr

ferror用于告诉用户C库的文件流是否有错误发生。当有错误发生时,ferror返回非零(1),反之返回0,当文件流非法,则返回-1。

clearerr用于清除文件流的文件结束位和错误位。

当一个程序读取一个文件到达文件末尾,文件将置EOF,另一个程序再次追加内容到文件中后,EOF需要被清除。

6 getc,fwrite,fread的返回值

下面这两个函数反之值不是char而是int,可能会返回-1(EOF)。

#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);

fread和fwrite的声明如下:

#include <stdlib.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

当看到返回值size\_t时,人们不要误解,它们的返回值不是成功读写的字节数,而是成功读写的对象数。size是每个对象的字节数,nmemb是要写入的对象的个数。

const char str[] = ``123456'';
// return 1
fwrite(str, strlen(str), 0, fp);

// return 9
fwrite(str, 1, strlen(str), fp);

By .