概念
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。
缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
为什么要引入缓冲区
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
缓冲区类型
缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。
1. 全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
一般磁盘文件是全缓冲的(缓冲区一般为4096个字节)。
2. 行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。
注意:换行符也被读入缓冲区。(缓冲区一般为1024个字节)
3. 不带缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
缓冲区特征
1. 缓存特征
ANSI C( C89 )要求缓存具有下列特征:
当且仅当标准输入和标准输出并不涉及交互设备时,它们才是全缓存的。
标准出错决不会是全缓存的。
但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还是行缓存的,以及标准输出是不带缓存的,还是行缓存的。
大部分系统默认使用下列类型的缓存:
标准出错是不带缓存的。
如果是涉及终端设备的流,则它们是行缓存的;否则是全缓存的。
我们经常要用到标准输入输出流,而ANSI C对stdin、stdout和stderr的缓存特征没有强行的规定,以至于不同的系统可能有不同的stdin、stdout和stderr的缓存特征。目前主要的缓存特征是:stdin和stdout是行缓存;而stderr是无缓存的。
2. 缓冲区的大小
如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是4096个字节的大小,这和计算机中的分页机制有关,因为进程在计算机中分配内存使用的就是分页与分段的机制,并且每个页的大小是4096个字节,因此通常情况下缓冲区的大小会设置为4096个字节的大小。
缓冲区大小由stdio.h头文件中的宏BUFSIZ定义,如果希望查看它的大小,包含头文件,直接输出它的值即可。
#include <stdio.h>
int main()
{
printf("<stdio.h> BUFSIZ:%d\n",BUFSIZ);
return 0;
}
缓冲区的大小是可以改变的,也可以将文件关联到自定义的缓冲区,详情可以查看setbug()和setvbuf()函数。
https://www.runoob.com/cprogramming/c-function-setbuf.html
https://www.runoob.com/cprogramming/c-function-setvbuf.html
3. 缓冲区的刷新(清空)
下列情况会引发缓冲区的刷新:
缓冲区满时;
行缓冲区遇到回车时;
关闭文件;
使用特定函数刷新缓冲区,如fflush()函数
我们上面提到标准输入输出是行缓冲,即一行满了才会刷新,那什么是刷新呢?刷新就是将数据从缓冲区取出来,真正能刷新,要满足什么条件呢?
1、满刷新,即一行满了(1024个字节)才会刷新;
2、遇到’\n’会刷新;
3、调用fflush()函数;
4、程序结束 fclose();
4. 缓冲类型和大小确认函数
//这是一个分别打印三个标准流和一个文件流的缓冲方式的应用实例
#include <stdio.h>
#include <stdlib.h>
#if defined(MACOS)
#define _IO_UNBUFFERED __SNBF
#define _IO_LINE_BUF __SLBF
#define _IO_file_flags _flags
#define BUFFERSZ(fp) (fp)->_bf._size
#else
#define BUFFERSZ(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
#endif
//以上是关于缓冲方式和缓冲区大小的预定义
void pr_stdio(const char *, FILE *);
//子函数声明
int main(int argc,char *argv[])
{
FILE *fp = NULL; //流文件结构指针
pr_stdio("stdin", stdin); //标准输入
pr_stdio("stdout", stdout); //标准输出
pr_stdio("stderr", stderr); //标准出错处理
printf("fopen error\n");
fp = fopen("file.txt", "w+");
fprintf(fp, "%s %s %s %d", "We", "are", "in", 2020);
pr_stdio("file", fp); //文件
return 0;
}
//测试缓冲输出函数
void pr_stdio(const char *name, FILE *fp)
{
printf("当前流是%s, ", name); //打印流的名称
if (fp->_IO_file_flags & _IO_UNBUFFERED)
{
printf("无缓冲\n");
}
else if (fp->_IO_file_flags & _IO_LINE_BUF)
{
printf("行缓冲\n");
}
else
{
printf("全缓冲\n");
}
printf(", 缓冲区大小 = %ld\n", BUFFERSZ(fp));
return;
}
https://blog.51cto.com/10638473/1983077
缓冲实例
我们以printf函数和stderr为例,先说明stdout(对应printf)是遇到换行符或缓冲区满之后或程序结束后才输出缓冲,stderr一般是无缓冲的:
/*
** 在我的实验环境中,缓冲区大小默认为1024
*/
#include <stdio.h>
int main()
{
while(1) {
printf(".");
fprintf(stderr, "1");
}
return 0;
}
运行结果:
(你可以数数,有1024个1,然后1024个’.’,…)
或者你可以通过用./a.out > t 2>&1
重定向标准输出和错误到文件t中,这样就可以很容易看到运行结果(因为程序跑的很快,很难找到开始运行出来的那一行),这时的缓冲区大小是4096个字节。
FILE结构定义
头文件
<sys/reent.h> defines __FILE, _fpos_t.
默认定义
struct __sFILE {
unsigned char *_p; /* current position in (some) buffer */
int _r; /* read space left for getc() */
int _w; /* write space left for putc() */
short _flags; /* flags, below; this FILE is free if 0 */
short _file; /* fileno, if Unix descriptor, else -1 */
struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */
int _lbfsize; /* 0 or -_bf._size, for inline putc */
#ifdef _REENT_SMALL
struct _reent *_data;
#endif
/* operations */
_PTR _cookie; /* cookie passed to io functions */
_READ_WRITE_RETURN_TYPE _EXFNPTR(_read, (struct _reent *, _PTR,
char *, _READ_WRITE_BUFSIZE_TYPE));
_READ_WRITE_RETURN_TYPE _EXFNPTR(_write, (struct _reent *, _PTR,
const char *,
_READ_WRITE_BUFSIZE_TYPE));
_fpos_t _EXFNPTR(_seek, (struct _reent *, _PTR, _fpos_t, int));
int _EXFNPTR(_close, (struct _reent *, _PTR));
/* separate buffer for long sequences of ungetc() */
struct __sbuf _ub; /* ungetc buffer */
unsigned char *_up; /* saved _p when _p is doing ungetc data */
int _ur; /* saved _r when _r is counting ungetc data */
/* tricks to meet minimum requirements even when malloc() fails */
unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
unsigned char _nbuf[1]; /* guarantee a getc() buffer */
/* separate buffer for fgetline() when line crosses buffer boundary */
struct __sbuf _lb; /* buffer for fgetline() */
/* Unix stdio files get aligned to block boundaries on fseek() */
int _blksize; /* stat.st_blksize (may be != _bf._size) */
_off_t _offset; /* current lseek offset */
#ifndef _REENT_SMALL
struct _reent *_data; /* Here for binary compatibility? Remove? */
#endif
#ifndef __SINGLE_THREAD__
_flock_t _lock; /* for thread-safety locking */
#endif
_mbstate_t _mbstate; /* for wide char stdio functions. */
int _flags2; /* for future use */
};
参考资料
https://www.cnblogs.com/pricks/p/3821832.html
https://www.cnblogs.com/mydomain/p/9817320.html
https://blog.csdn.net/snowlyw/article/details/80963494