当前位置: 亚洲城ca88 > ca88 > 正文

C语言清空输入缓冲区的N种方法对比,linux下文件

时间:2019-07-04 23:03来源:ca88
C语言中有几个基本输入函数: 转自: linux下的文件的缓冲 在linux下高级文件编程中也讲到:   所谓文件写缓冲,是指文件流在执行输出操作时,并不立刻将数据写入文件,而是先把数

C语言中有几个基本输入函数:

转自:

linux下的文件的缓冲
在linux下高级文件编程中也讲到:
   所谓文件写缓冲,是指文件流在执行输出操作时,并不立刻将数据写入文件,而是先把数据累计到缓冲区,再以块为单位批量输出到文件中,同理,文件读缓冲是指 文件流在执行输入操作时,以块为单位读取文件内容,多余的数据存储在内存中。如果下次读操作的内容刚好在同一块中,则可以直接返回结果,避免一次输入操 作。通过缓冲技术,可以减少低级I/O函数read和write函数的调用次数,从而大大提高软件执行效率。
  1)缓冲模式
   标准文件编程库采用FILE类型描述文件流,与低级I/O函数相比,最大的特性就是应用及增加了缓冲功能(低级I/O函数只使用了文件系统自带的缓冲功能),文件的输入输出以"缓冲块"为单位批量完成,并且根据"缓冲块"大小,提供了三种缓冲模式。
   (1)全缓冲(_IOFBF):一般读写普通磁盘文件采用全缓冲模式。
   (2)行缓冲(_IOLBF):比如调用fgets函数从标准输入流stdin中输入字符,当且仅当客户输入回车换行时,函数才返回。
   (3)无缓冲(_IONBF):比如stderr采用无缓冲模式;
2)缓冲函数
#include<stdio.h> 
void setbuf(FILE *stream,char *buf); 
int setvbuf(FILE *stream,char *buf,int type,size_t size); 
int fflush(FILE *stream); 
    setbuf设置文件流stream的缓冲区,参数buf指向一个大小为BUFSIZ的内存块,调用成功后,文件流stream使用该内存块作为新的缓冲区。倘若buf是空指针NULL,文件流stream的缓冲将被完全关闭。缓冲区内存块的定义一般为:
   char buf[BUFSIZ]; ---其中BUFSIZ是stdio.h中的常数,代表缓冲区的大小,常为256的整数倍。
   setvbuf设置了文件流stream的缓冲区和缓冲模式,缓冲模式由参数type确定.
_IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
_IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。   _IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
   任何时候,都可以使用fflush刷新缓冲区,并将缓冲区的内容强制输出到文件中,参数stream指明了更新的 文件流,当其值为NULL时,系统将刷新全部文件流的缓冲区。
  实例:
#include<stdio.h> 
void main() 

  printf("1---1"); 
  /*fflush(stdout);*/ 
  fprintf(stderr,"2---2"); 
  printf("3---3n"); 
  fprintf(stderr,"4--4n"); 

 编译运行:
[email protected]:~# gcc -o buf1 buf1.c
[email protected]:~# ./buf1
  2---2    1---1    3---3 
  4--4 
如果去掉fflush的注释,则结果如下:
[email protected]:~# gcc -o buf1 buf1.c
[email protected]:~# ./buf1
  1---1    2---2    3---3 
  4--4 
这是因为stdout是行缓冲,stderr是无缓冲,仔细理解。
作者“pstary”

//获取字符系列  

int fgetc(FILE *stream);  

int getc(FILE *stream);  

int getchar(void);  

//获取行系列  

char *fgets(char * restrict s, int n, FILE * restrict stream);  

char *gets(char *s);//可能导致溢出,用fgets代替之。  

//格式化输入系列  

int fscanf(FILE * restrict stream, const char * restrict format, …);  

int scanf(const char * restrict format, …);  

int sscanf(const char * restrict str, const char * restrict format, …);

这里仅讨论输入函数在标准输入(stdin)情况下的使用。纵观上述各输入函数,

C语言中有几个基本输入函数:

在linux下高级文件编程中也讲到: 所谓文件写缓冲,是指文件流在执行输出操作时,并不立刻将数据写入文件,而是先...

  • 获取字符系列的的前三个函数fgetc、getc、getchar。以getchar为例,将在stdin缓冲区为空时,等待输入,直到回车换行时函数返回。若stdin缓冲区不为空,getchar直接返回。getchar返回时从缓冲区中取出一个字符,并将其转换为int,返回此int值。
//获取字符系列

int fgetc(FILE *stream);

int getc(FILE *stream);

int getchar(void);

//获取行系列

char *fgets(char * restrict s, int n, FILE * restrict stream);

char *gets(char *s);//可能导致溢出,用fgets代替之。

//格式化输入系列

int fscanf(FILE * restrict stream, const char * restrict format, …);

int scanf(const char * restrict format, …);

int sscanf(const char * restrict str, const char * restrict format, …);

这里仅讨论输入函数在标准输入(stdin)情况下的使用。纵观上述各输入函数,

MINGW 4.4.3中FILE结构体源码

  • 获取字符系列的的前三个函数fgetc、getc、getchar。以getchar为例,将在stdin缓冲区为空时,等待输入,直到回车换行时函数返回。若stdin缓冲区不为空,getchar直接返回。getchar返回时从缓冲区中取出一个字符,并将其转换为int,返回此int值。
typedef struct _iobuf  

{  

 char* _ptr;//指向当前缓冲区读取位置  

 int _cnt;//缓冲区中剩余数据长度  

 char* _base;  

 int _flag;  

 int _file;  

 int _charbuf;  

 int _bufsiz;  

 char* _tmpfname;  

} FILE;

各编译器实现可能不一样,这里获取字符系列函数只用到_ptr和_cnt。

MINGW 4.4.3中FILE结构体源码

MINGW 4.4.3中getchar()实现

typedef struct _iobuf

{

   char*   _ptr;//指向当前缓冲区读取位置

   int _cnt;//缓冲区中剩余数据长度

   char*   _base;

   int _flag;

   int _file;

   int _charbuf;

   int _bufsiz;

   char*   _tmpfname;

} FILE;

各编译器实现可能不一样,这里获取字符系列函数只用到_ptr和_cnt。
__CRT_INLINE int __cdecl __MINGW_NOTHROW getchar (void)

{

  return (--stdin->_cnt >= 0)

    ?  (int) (unsigned char) *stdin->_ptr  

    : _filbuf (stdin);

}

MINGW 4.4.3中getchar()实现

其中stdin为FILE指针类型,在MINGW 4.4.3中,getc()和getchar()实现为内联函数,fgetc()实现为函数。顺便说一句,C99标准中已经加入对内联函数的支持了。

__CRT_INLINE int __cdecl __MINGW_NOTHROW getchar (void)

{

  return (--stdin->_cnt >= 0)

    ?  (int) (unsigned char) *stdin->_ptr  

    : _filbuf (stdin);

}
  • 获取行系列的fgets和gets,其中由于gets无法确定缓冲区大小,常导致溢出情况,这里不推荐也不讨论gets函数。对于fgets函数,每次敲入回车,fgets即返回。fgets成功返回时,将输入缓冲区中的数据连换行符’n’一起拷贝到第一个参数所指向的空间中。若输入数据超过缓冲区长度,fgets会截取数据到前n-1(n为fgets第二个参数,为第一个参数指向空间的长度),然后在末尾加入’n’。因此fgets是安全的。通常用fgets(buf, BUF_LEN, stdin);代替gets(buf);。
  • 格式化输入系列中,fscanf从文件流进行格式化输入很不好用。常用的还是scanf,格式化输入系列函数舍去输入数据(根据函数不同可能是标准输入也可能是字符串输入,如:sscanf)前的空白字符(空格、制表符、换行符)直至遇到非空白字符,然后根据格式参数尝试对非空白字符及后续字符进行解析。该系列函数返回成功解析赋值的变量数,若遇文件尾或错误,返回EOF。

其中stdin为FILE指针类型,在MINGW 4.4.3中,getc()和getchar()实现为内联函数,fgetc()实现为函数。顺便说一句,C99标准中已经加入对内联函数的支持了。

=================分 割 线=================

  • 获取行系列的fgets和gets,其中由于gets无法确定缓冲区大小,常导致溢出情况,这里不推荐也不讨论gets函数。对于fgets函数,每次敲入回车,fgets即返回。fgets成功返回时,将输入缓冲区中的数据连换行符’n’一起拷贝到第一个参数所指向的空间中。若输入数据超过缓冲区长度,fgets会截取数据到前n-1(n为fgets第二个参数,为第一个参数指向空间的长度),然后在末尾加入’n’。因此fgets是安全的。通常用fgets(buf, BUF_LEN, stdin);代替gets(buf);。
  • 格式化输入系列中,fscanf从文件流进行格式化输入很不好用。常用的还是scanf,格式化输入系列函数舍去输入数据(根据函数不同可能是标准输入也可能是字符串输入,如:sscanf)前的空白字符(空格、制表符、换行符)直至遇到非空白字符,然后根据格式参数尝试对非空白字符及后续字符进行解析。该系列函数返回成功解析赋值的变量数,若遇文件尾或错误,返回EOF。

提到缓冲区,就不得不提setbufsetvbuf两个缓冲区设置函数,其声明如下:

=================分 割 线=================

void setbuf(FILE * restrict stream, char * restrict buf);  

int setvbuf(FILE * restrict stream, char * restrict buf, int mode, size_t size);

提到缓冲区,就不得不提setbufsetvbuf两个缓冲区设置函数,其声明如下:

setvbuf的mode参数有:

void setbuf(FILE * restrict stream, char * restrict buf);

int setvbuf(FILE * restrict stream, char * restrict buf, int mode, size_t size);
  • _IOFBF(满缓冲):缓冲区空时读入数据;缓冲区满时向流写入数据。
  • _IOLBF(行缓冲):每次从流读入一行数据或向流写入数据。如:stdio,stdout
  • _IONBF(无缓冲):直接从流读入数据,或者直接向流写入数据,而没有缓冲区。如:stderr

setvbuf的mode参数有:

setbuf(stream, buf);在:

  • _IOFBF(满缓冲):缓冲区空时读入数据;缓冲区满时向流写入数据。
  • _IOLBF(行缓冲):每次从流读入一行数据或向流写入数据。如:stdio,stdout
  • _IONBF(无缓冲):直接从流读入数据,或者直接向流写入数据,而没有缓冲区。如:stderr
  • buf == NULL:等价于(void)setvbuf(stream, NULL, _IONBF, 0);
  • buf指向长度为BUFSIZ的缓冲区:等价于(void)setvbuf(stream, buf, _IOFBF, BUFSIZ);

setbuf(stream, buf);在:

注:BUFSIZ宏在stdio.h中定义。

  • buf == NULL:等价于(void)setvbuf(stream, NULL, _IONBF, 0);
  • buf指向长度为BUFSIZ的缓冲区:等价于(void)setvbuf(stream, buf, _IOFBF, BUFSIZ);

 

注:BUFSIZ宏在stdio.h中定义。

这里还要提一下传说中的setbuf经典错误,在《C陷阱和缺陷》上有提到:

 

int main()  

{  

    int c;  

    char buf[BUFSIZ];  

    setbuf(stdout,buf);  

    while((c = getchar()) != EOF)  

        putchar(c);  



    return 0;  

}

这里还要提一下传说中的setbuf经典错误,在《C陷阱和缺陷》上有提到:

问题是这样的:程序交回控制给操作系统之前C运行库必须进行清理工作,其中一部分是刷新输出缓冲,但是此时main函数已经运行完毕,buf缓冲区作用域在main函数中,此时buf字符数组已经释放,导致输出诡异乱码。

int main()

{

    int c;

    char buf[BUFSIZ];

    setbuf(stdout,buf);

    while((c = getchar()) != EOF)

        putchar(c);



    return 0;

}

解决方案:可以将buf设置为static,或者全局变量,或者调用malloc来动态申请内存。

问题是这样的:程序交回控制给操作系统之前C运行库必须进行清理工作,其中一部分是刷新输出缓冲,但是此时main函数已经运行完毕,buf缓冲区作用域在main函数中,此时buf字符数组已经释放,导致输出诡异乱码。

=================分 割 线=================

解决方案:可以将buf设置为static,或者全局变量,或者调用malloc来动态申请内存。

下面来看看几种流行的缓冲区清空方法:

=================分 割 线=================

  • fflush(stdin);式

下面来看看几种流行的缓冲区清空方法:

由C99标准文档中:

  • fflush(stdin);式
If stream points to an output stream or an update stream in which the most recent  

operation was not input, the fflush function causes any unwritten data for that stream  

to be delivered to the host environment to be written to the file; otherwise, the behavior is  

undefined.

由C99标准文档中:

可以看出fflush对输入流为参数的行为并未定义。但由MSDN上的fflush定义:

If stream points to an output stream or an update stream in which the most recent

operation was not input, the fflush function causes any unwritten data for that stream

to be delivered to the host environment to be written to the file; otherwise, the behavior is

undefined.
If the file associated with stream is open for output, fflush writes to that file the   

contents of the buffer associated with the stream. If the stream is open for input,   

fflush clears the contents of the buffer. 

可以看出fflush对输入流为参数的行为并未定义。但由MSDN上的fflush定义:

可以看出fflush(stdin)在VC上还是有效地!鉴于各编译器对fflush的未定义行为实现不一样,不推荐使用fflush(stdin)刷新输入缓冲区。

If the file associated with stream is open for output, fflush writes to that file the 

contents of the buffer associated with the stream. If the stream is open for input, 

fflush clears the contents of the buffer. 
  • setbuf(stdin, NULL);式

可以看出fflush(stdin)在VC上还是有效地!鉴于各编译器对fflush的未定义行为实现不一样,不推荐使用fflush(stdin)刷新输入缓冲区。

由前面对setbuf函数的介绍,可以得知,setbuf(stdin, NULL);是使stdin输入流由默认缓冲区转为无缓冲区。都没有缓冲区了,当然缓冲区数据残留问题会解决。但这并不是我们想要的。

  • setbuf(stdin, NULL);式
  • scanf("%*[^n]");式(《C语言程序设计 现代方法 第二版》中提到)

由前面对setbuf函数的介绍,可以得知,setbuf(stdin, NULL);是使stdin输入流由默认缓冲区转为无缓冲区。都没有缓冲区了,当然缓冲区数据残留问题会解决。但这并不是我们想要的。

这里用到了scanf格式化符中的“*”,即赋值屏蔽;“%[^集合]”,匹配不在集合中的任意字符序列。这也带来个问题,缓冲区中的换行符’n’会留下来,需要额外操作来单独丢弃换行符。

  • scanf("%*[^n]");式(《C语言程序设计 现代方法 第二版》中提到)
  • 经典式

这里用到了scanf格式化符中的“*”,即赋值屏蔽;“%[^集合]”,匹配不在集合中的任意字符序列。这也带来个问题,缓冲区中的换行符’n’会留下来,需要额外操作来单独丢弃换行符。

  • 经典式
int c;  

while((c = getchar()) != 'n' && c != EOF);

由代码知,不停地使用getchar()获取缓冲区中字符,直到获取的字符c是换行符’n’或者是文件结尾符EOF为止。这个方法可以完美清除输入缓冲区,并且具备可移植性。

int c;

while((c = getchar()) != 'n' && c != EOF);

由代码知,不停地使用getchar()获取缓冲区中字符,直到获取的字符c是换行符’n’或者是文件结尾符EOF为止。这个方法可以完美清除输入缓冲区,并且具备可移植性。

编辑:ca88 本文来源:C语言清空输入缓冲区的N种方法对比,linux下文件

关键词: 亚洲城ca88