今天我们围绕标准I/O做一些详细的讨论。
首先,我们先来看一些重要的概念。
流和文件指针
文件I/O操作都是针对文件描述符进行的,相对的,标准I/O的操作都是围绕一种叫做流(stream)的东西进行的,当使用标准 I/O 库打开或创建一个文件时,我们就已使一个流与这个文件相关联,通过流的读入和输出完成所需要的 I/O操作。
用fopen打开一个流会返回一个指向FILE对象的指针,即文件指针,FILE对象通常是一个结构,包含了标准I/O库为管理该流需要的所有信息,如用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区长度、出错标志等。
系统为每个进程预定义了3个可以自动被使用的流:标准输入、标准输出和标准错误,这3个标准I/O流通过预定义的文件指针stdin、stdout、stderr加以引用。
缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write系统调用的次数,缓冲类型有三种:
(1)全缓冲:填满标准I/O缓冲区后才进行实际I/O操作,磁盘中的文件通常是全缓冲,术语冲洗(flush)说明标准I/O缓冲区的写操作。
(2)行缓冲:在输入和输出中遇到换行符或行缓冲区被填满时执行实际I/O操作,当流涉及一个终端(如标准输入和标准输出)时,通常使用行缓冲。
(3)不带缓冲:标准I/O不对字符进行缓冲存储,标准错误stderr通常默认为不带缓冲的。
ISO C规定:
当且仅当stdin和stdout不指向交互式设备时,他们才是全缓冲。
stderr决不会是全缓冲。
Linux系统默认:
stderr是不带缓冲的。
若流指向终端设备,则是行缓冲,否则为全缓冲。
对于一个给定的已经打开且尚未执行任何操作的流,我们可以调用setbuf和setvbuf来更改系统默认的缓冲类型。
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
/*返回值:若成功。返回0;若出错,返回非0*/
我们来看一下这两个函数的区别:
setbuf只提供两种功能——打开或关闭由第一个参数fp指定的流的缓冲机制。若为打开缓冲,第二个参数buf必须指向一个长度为BUFSIZ的缓冲区,BUFSIZ定义在stdio.h中,至于调用函数之后究竟是全缓冲还是行缓冲,那就取决于该流是与终端相关还是与文件相关了;若想关闭该流的缓冲,只需把buf设为NULL即可。
setvbuf功能就要强大一些,它多了两个参数mode和size,不仅可以实现setbuf的功能,还可以在打开缓冲时由mode指定具体的指定缓冲类型,由size指定缓冲区的长度。
mode参数 | 缓冲类型 |
---|---|
_IOFBF | 全缓冲 |
_IOLBF | 行缓冲 |
_IONBF | 不带缓冲 |
关于这两个函数更详细的动作,可以看下下面这张表,这里就不再多费口舌了。
关键字restrict:
大家可能都注意到了,这两个函数的参数中都带有restrict这个关键字,查了下发现是C99新增的,它只用于限定指针,作用是告诉编译器,所有修改该指针所指向内容的操作都必须通过该指针进行,而不能通过其它途径(其它变量或指针)来修改。这样做的好处是能帮助编译器进行更好的代码优化,生成更有效率的汇编代码。
接下来