首页 > 代码库 > 标准缓冲I/0及其可能遇到的错误

标准缓冲I/0及其可能遇到的错误

在文件I/O中所有函数都是针对文件描述符的,对于标准I/O库,他们的操作则是围绕流进行的。当用标准I/O库打开或创

建一个文件时,我们使一个流与一个文件相关联。

当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理

该流所需要的所有信息,包括:用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的

字符数以及出错标志等等。

在文件I/O中已经介绍标准I/O是提供缓冲区的。标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。标

准I/O库处理很多细节,例如缓冲区分配,以优化长度执行I/O等。

标准I/O提供了三种类型的缓冲:

1)全缓冲。这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全

缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。

2)行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(标

准I/O fputc函数),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(例如标准输入和标准输出),通常使

用行缓冲。

对于行缓冲有两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的、所以只要填满了缓冲区,那么即

使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从(a)一个不带缓冲的流,或者(b)一

个行缓冲的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。

3)不带缓冲。标准I/O库不对字符进行缓冲存储。例如,如果用标准I/O函数fputs写15个字符到不带缓冲的流中,则该函数

很可能用上面讲述的write系统调用函数将这些字符立即写至关联的打开的文件上。

对任何一个给定的流,我们可以通过以下两个函数更改缓冲类型。

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. void setbuf(FILE* restrict fp, char* restrict buf);  
  3. int setvbuf(FILE* restrict fp, char* restrict buf, int mode, size_t size); //如果成功返回0,出错则返回非0.  
可以使用setbuf函数打开或关闭缓冲机制,为了带缓冲进行IO,参数buf必须制定一个长度为BUFSIZ的缓冲区(这就是为什么

没有在setbuf函数的参数中指定buf的长度),通常在此之后该流就是全缓冲的,但是如果该流与一个终端相关,那么某些系统也可

以将其设置为行缓冲。为了关闭缓冲,将buf设置为NULL。

使用setbuf,可以精确地指定所需的缓冲类型。mode的取值及其代表的含义如下:

  _IOFBF 全部缓冲
  _IOLBF 行缓冲
  _IONBF 不缓冲

注意:

如果指定一个不带缓冲的流,则忽略buf和size参数。

如果指定全缓冲和行缓冲,则buf和size可选择地指定一个缓冲区及其长度。

如果该流是带缓冲的,而buff是NULL,则标准IO库将自动地为该流分配适当长度的缓冲区(长度为BUFSIZ指定的值)。


当标准I/O函数遇到fork函数(多进程)时可能会因为其缓冲问题得到不同的结果

看下面的代码:

技术分享

在fork函数之前,我们执行一次write函数和printf(“before fork”)。按道理,我们的结果应该只会得到一次输出。

但是运行的结果却是如下:

  技术分享

从结果中我们看到 before fork 执行了两次。这是为什么呢?

这是由于缓冲区的缘故.write函数时不带缓冲区的,所以在调用write函数之后会立刻执行I/O操作,将结果输出到标准输出上。

然而printf函数(标准I/O函数)是带缓冲的,在此处是行缓冲(交互式程序中是行缓冲),当执行完printf("before fork")函数之后,系统会将结果存放在缓冲区中(因为没有遇到换行,同时也没有超过行缓冲的大小)。接下来执行fork函数之后,子进程会将上面的缓冲区的内容也复制到自己的缓冲区中,所以在父进程和子进程的缓冲区中都存在。在程序退出时,系统会将缓冲区的内容刷新到标准输出上。

标准缓冲I/0及其可能遇到的错误