C语言标准输入输出缓冲区

39 篇文章 3 订阅

概念

缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

为什么要引入缓冲区
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值