《UNIX 环境高级编程》笔记 - 标准 IO 库
不仅是 UNIX,很多其它操作系统都实现了标准 I/O 库,这个库是由 ISO C 标准定义的。
流和 FILE 对象
文件 I/O 函数都是围绕着文件描述符进行的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符用于后续的 I/O 操作。
对于标准 I/O 库而言,它们的操作是围绕流(stream)进行的。当用标准 I/O 库打开或者创建一个文件时,我们使用一个流与一个文件相关联。
对于 ASCII 字符集,一个字符用一个字节表示。对于国际字符集(例如 UTF-8),一个字符可用多个字节(宽字节)表示。标准 I/O 文件流即可以用于单字节的,也可用于多字节的字符集。流的方向(orientation of stream)决定了它所读写的字符是单字节还是多字节的。
调用 fwide
函数可以设置流的方向。需要注意的是,fwide
函数并不会改变已经定向的流的方向。
当打开一个流时,标准 I/O 函数 fopen
返回一个指向 FILE 对象的指针。FILE 对象通常是一个结构,它包含了标准 I/O 库为管理该流需要的所有信息,包括用于实际 I/O 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志。
标准输入、标准输出和标准错误
每个进程都预定义了 3 个流,并且这 3 个流可以自动地被进程所使用,它们是:标准输入、标准输出、标准错误。
这 3 个标准 I/O 流通过预定义的文件指针 stdin、stdout、stderr 加以引用,文件指针定义在头文件 <stdio.h>
中。
缓冲
标准 I/O 库提供缓冲的目的是尽可能减少使用 read
函数和 write
函数的调用次数。标准 I/O 库提供了以下 3 种类型的缓冲:
全缓冲:标准 I/O 库将会在缓冲区填满之后,再进行实际的 I/O 操作。术语 flush 表示标准 I/O 缓冲区的写操作;
行缓冲:标准 I/O 库将会在输入和输出过程中遇到换行符时,再进行实际的 I/O 操作。
不带缓冲:标准 I/O 库不会对字符进行缓冲。标准错误流 stderr 通常是不带缓冲的。
调用 setbuf
函数或 setlinebuf
函数可以更改流的缓冲类型。
打开流
调用 fopen
函数、fdopen
函数或 freopen
函数可以打开一个标准 I/O 流。
fopen
函数打开指定的路径名为 pathname 的文件。
fdopen
函数打开指定的文件描述符 fd 上的文件。这个函数通常用于创建管道函数和网络通信函数返回的文件描述符。因为这些特殊类型的文件不能用标准 I/O 库中的 fopen
函数打开,因此必须先调用设备专用函数用以获取一个文件描述符,然后用 fdopen
函数将标准 I/O 流与该文件描述符相关联。
freopen
函数打开指定的路径名为 pathname 的文件,并且关联到指定的流 stream 上。如果指定的流 stream 已经打开,则会先关闭它。
fopen
函数和 freopen
函数是 ISO C 的一部分,因为 ISO C 并不涉及文件描述符,因此只有 POSIX 才有 fdopen
函数。
mode 参数指定对该 I/O 流的读写方式,ISO C 规定 mode 参数可以用 15 种不同的值。
读和写流
一旦打开了流,则可以使用 3 种不同类型的非格式化 I/O 对其进行读、写操作:
每次只操作一个字符的 I/O:
fgetc
函数和fputc
函数在每次执行 I/O 操作时,只会读写一个字符;每次操作一行字符的 I/O:
fgets
函数和fputs
函数在每次执行 I/O 操作时,会以换行符为标记,读取一行的内容;直接 I/O(二进制 I/O):
fread
函数和fwrite
函数在每次执行 I/O 操作时,会读写特定数量的对象,每个对象具有指定的长度。这两个函数通常用于从二进制文件中读写一个结构。
输入函数
调用 fgetc
函数、getc
函数或 getchar
函数可以读取一个字符。
fgetc
函数可以作为地址,被当作参数传递给另一个函数。
getc
函数等同于 fgetc
函数,它可能是通过宏来实现的。
getchar
函数等同于 getc(stdin)
函数调用。
fgetc
函数、getc
函数和 getchar
函数在返回下一个字符时,会将 unsigned char 类型转换为 int 类型。
判断是出错还是达到文件尾端
不管是出错还是达到文件尾端,这 3 个函数都会返回同样的值。为了区分这两种不同的场景,必须调用 ferror
函数或 feof
函数。
压送字符回到流中
从流中读取数据之后,可以调用 ungetc
函数将字符再压送回到流中。用 ungetc
函数压送回字符时,并没有真正将它们写到底层文件或者设备中,只是将它们写回到标准 I/O 库的缓冲区中。
压送回到流中的字符之后可以再从流中被读取,但读取字符的顺序与压送回的字符顺序相反。虽然 ISO C 允许实现库支持任意次数的将字符压送回到流中的操作,但是它要求实现库一次只能压送一个字符。压送回到流中的字符不一定是上次读取的字符,但不能压送 EOF 字符。
当正在读取一个输入流,并进行某种形式的分词或者记号切分操作时,会经常用到 ungetc
函数。
输出函数
调用 fputc
函数、putc
函数或 putchar
函数可以写入一个字符。
每次一行 I/O
调用 fgets
函数可以读取一行字符。
调用 fputs
函数和 puts
函数可以写入一行字符。
二进制 I/O
调用 fread
函数和 fwrite
函数可以读写一个完整的结构。
fread
函数和 fwrite
函数通常被用于:读写一个二进制数组、读写一个结构。
定位流
调用 fseek
函数、ftell
函数、rewind
函数、fgetpos
函数和 fsetpos
函数可以查询和设置流的偏移量。
对于一个二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位。但对于一个文本文件,它的文件当前位置可能不能简单地以字节偏移量来度量。
ftell
函数可以获取流的偏移量,fseek
函数可以设置流的偏移量。rewind
函数可以重置流的偏移量为文件的起始位置。
fgetpos
函数和 fsetpos
函数与 ftell
函数和 fseek
函数类似,不过它们是 ISO C 标准引入的。
ftello
函数和 fseeko
函数支持以 off_t 类型来设置流的偏移量。
格式化 I/O
格式化输出
调用 printf
函数、fprintf
函数、dprintf
函数、sprintf
函数和 snprintf
函数可以以格式化的方式输出 I/O 流。
vprintf
函数、vfprintf
函数、vdprintf
函数、vsprintf
函数和 vsnprintf
函数也可以以格式化的方式输出 I/O 流,不过它们的可变参数列表变成了 vs_list 类型。
格式化输入
调用 scanf
函数、fscanf
函数和 sscanf
函数可以以格式化的方式输入 I/O 流。
vscanf
函数、vfscanf
函数和 vsscanf
函数也可以以格式化的方式输入 I/O 流,不过它们的可变参数列表变成了 vs_list 类型。
实现细节
在 UNIX 中,标准 I/O 库最终都要调用文件 I/O 中的函数。每个标准 I/O 流中都有一个与其相关联的文件描述符。
调用 fileno
函数可以获取与流相关的文件描述符。
临时文件
调用 tmpnam
函数或 tmpfile
函数可以创建一个临时文件。
内存流
调用 fmemopen
函数、open_memstream
函数或 open_wmemstream
函数可以创建一个内存流。
fmemopen
函数允许调用者将提供的缓冲区用于内存流。
open_memstream
函数可以创建面向字节的内存流, open_wmemstream
函数可以创建面向宽字节的内存流。
标准 I/O 库的替代软件
标准 I/O 库的一个不足之处是效率不高,这与它需要复制的数据量有关系。当使用每次读写一行的 fgets
函数和 fputs
函数时,通常需要复制两次数据:第一次是在内核和标准 I/O 库缓冲区之间,第二次是在标准 I/O 缓冲区和用户程序中之间。(源码位于 iofgets.c 和 iogetline.c 两个源文件中。)
可替代标准 I/O 库的软件包有:
fio:快速 I/O 库;
sfio 库;
ASI(Alloc Stream Interface):提供了
mmap
函数;uClibc C 库和 Newlib C 库:适用于内存较小的系统,例如嵌入式系统。
最后更新于