关于标准IO缓冲区的问题

关于标准IO缓冲区的问题

按照标准IO缓冲区可以分为三类:

不缓存类型:

  • 一旦有数据,直接将数据写入到文件

行缓冲类型:

  • 同全缓冲类型
  • 遇到\n时,将数据写入文件

全缓冲类型:

  • 当程序结束,将数据冲洗到文件
  • 当遇见fflush(),将数据冲洗到文件
  • 当文件关闭时,将数据冲洗到文件
  • 当遇到读取操作,将数据冲洗到文件
  • 当改变缓冲区的类型时,将数据冲洗到文件
  • 当缓冲区满了,将数据冲洗到文件

对于标准输出而言,默认是行缓冲
对于标准出错而言,默认是不缓存
对于普通文件而言,默认是全缓冲

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    // w+ 以读写的方式打开文件,若文件存在则清空文件数据,若文件不存在,则创建文件
    FILE *fp = fopen("_a.txt", "w+");

    // 将数据写入文件
    fputs("123", fp);
    
    //暂停程序不让,不让程序正常结束,观察a.txt文件内容
    pause();
    return 0;
}

我们能看到文件被创建,但是文件内没有内容。
我们来分析一下,我们此时是以普通文件操作,此时是默认的全缓冲,且调用了pause()函数,程序没有正常的结束,所以能猜想到此刻的内容是在缓冲区里,没有被冲洗至文件中。如果我们把pause()进行注释,在执行该程序,我们能看到我们要写入的数据出现在_a.txt文件中。这也是全缓冲类型的第一次情况,当程序结束,将数据读取到文件。

接下来我们来看一下这段代码:

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("_a.txt", "w+");

    fputs("123", fp);
    fflush(fp);

    pause();
    return 0;
}

印证第二种情况我们直接用冲洗函数fflush(),能看到数据成功读取到文件中。

第三种情况,在fput函数和pause函数中间,调用fclose(),关闭文件。

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("_a.txt", "w+");

    fputs("123", fp);
    fclose(fp);

    pause();
    return 0;
}

第四种情况,当遇到读取操作,将数据冲洗到文件。

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("_a.txt", "w+");

    fputs("123", fp);
    fgetc(fp);

    pause();
    return 0;
}

接下来我们看一下通过setvbuf()改变缓冲区的类型,进而对缓冲区的冲洗操作。

简单的介绍一下:setvbuf函数

image
这图片是我在man手册截图下来的。

int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
stream — 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
buffer — 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
mode — 这指定了文件缓冲的模式。

_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 无缓冲

size –这是缓冲的大小,以字节为单位。

返回值:如果成功,则该函数返回 0,否则返回非零值。

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("_a.txt", "w+");
 // setvbuf(fp, NULL, _IONBF, 0);
    fputs("123", fp);

    setvbuf(fp, NULL, _IONBF, 0);

    pause();
    return 0;
}

我们这里的语句是setvbuf(fp, NULL, _IONBF, 0),把普通文件默认的全缓冲,转换成无缓冲,数据在文件中有所显示,所以符合我们的第五条理论。这是为了突出我们的第五条理论,因此将该语句放置在fput语句的后面,当然如果我们在setvbuf(fp, NULL, _IONBF, 0)后面写上相同的fputs语句或者将setvbuf(fp, NULL, _IONBF, 0),放置至fopen语句后面也是会相同的效果。不过第二种情况的理解不同,此刻能成功读取数据不是因为缓冲区类型的转换,而是因为它此刻已经被定义为无缓冲,所以数据直接读取至文件中。

我们这里不妨试一试自己定义一个100字节的buf缓冲区,且在读取数据之前就将缓冲区类型转换至行缓存,观察是否能通过\n来将数据读取至文件当中去。(冲洗行缓冲)。


#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("_a.txt", "w+");
    char buf[1024];
    
    setvbuf(fp, buf, _IOLBF, 1024);

    //这里是行缓冲,如果要将数据冲洗至普通文件,注意要加\n
    fputs("123\n", fp);
    
    pause();
    return 0;
}

总之,滞留在缓冲区中的数据有时被称为脏数据(dirty data),脏数据的存在代表程序操作的结果与文件真实状态不一致,若未正常冲洗这些脏数据就退出程序则有可能会造成数据丢失,我们在使用IO对数据操作时要注意对缓冲区的问题

Tags: