打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
fflush 函数

FROM http://blog.chinaunix.net/uid-21651805-id-3482290.html

2013.01

如果给出的文件流是一个输出流,那么fflush()把输出到缓冲区的内容写入文件. 如果给出的文件流是输入类型的,那么fflush()会清除输入缓冲区. fflush()在调试时很实用,特别是对于在程序中输出到屏幕前发生错误片段时. 直接调用 fflush( STDOUT )输出可以保证你的调试输出可以在正确的时间输出. printf( "Before first call\n" );

fflush( STDOUT );

shady_function();

printf( "Before second call\n" );

fflush( STDOUT );

dangerous_dereference();

fflush是从内存缓冲区将数据写到内核缓冲,fsync再将内核缓冲写到磁盘。 前者是针对用户空间,后者是针对内核空间,不是一个概念。

1.

flush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃

fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上。

2.

fflush的真正作用就是立即将缓冲区中的内容输出到设备。正因为这样,所以只能在写入文件的时候使用fflush。在读取文件时使用fflush是不好的编程方法,因为那样的代码在一些环境下可能正常工作,但在另一些环境下则会出错,这导致程序不可移植。

flush即清空缓冲,在慢速设备上使用缓存是一种提升数据处理效率的手段,flush的作用是将内存中缓冲的内容实际写入外存媒介

详见MSDN的Kernel32!FlushFileBuffers

fclose后未必会flush一次的,操作系统会在CPU空闲的时候执行flush

3.fflush不应该在读取文件的时候用,而应该在写入文件的时候用。

fflush会清空缓冲区,fclose在关闭文件之前也会清空缓冲区。如果使用exit函数退出程序,或者main函数返回,则所有打开后没有关闭的文件会自动关闭,关闭时也会清空缓冲区。

通常,只有在程序非正常结束的情况下,缓冲区才不会被清除。

FILE* pFile;

int* p = NULL;

pFile = fopen("out.txt", "w");

fprintf(pFile, "%s", "hello");

*p = 10; // 在这里,程序会出错,然后退出

// 此时hello几个字符很可能还在缓冲区中,而没有实际的输出到设备

// 所以文件中很可能没有数据

如果在fprintf一句后面加上fflush(pFile);则即使以后程序出错,因为数据已经写入,所以文件中一定会有数据存在。

fflush的真正作用就是立即将缓冲区中的内容输出到设备。正因为这样,所以只能在写入文件的时候使用fflush。在读取文件时使用fflush是不好的编程方法,因为那样的代码在一些环境下可能正常工作,但在另一些环境下则会出错,这导致程序不可移植。

在有些时候,你会发现你的输出并没有按照你代码的流程输出, 比如说当你用C语言写了做了一个写文件的函数并印出一些信息,供perl或php等调用.而这时你会发现, perl或php并没有获取到你C语言中的打印信息.而代码什么都没有错误,在linux终端也可以正确打印出来. 这时可真是郁闷吧. 那么, 记住了, 这很大可能是没有更新你的缓冲区了. 这时int fflush(FILE *stream)这个函数就有用了. 下面看看这个函数的信息:

下面还是用例子来说明吧:

文件名: fflush.c

#include

#include

#include

int writefile(char *filename, char *buf, int length)

{

FILE *fp;

int j = 0;

size_t i = 0;

fp = fopen(filename, "w");

if (fp == NULL) {

printf("\topen file error!\n");

return -1;

} else {

while (length != 0) {

i = fwrite(&buf[j], 1, 1, fp);

length--;

j ;

}

}

fflush(fp);

fclose(fp);

return 0;

}

int readfile(char *filename, char *buf)

{

FILE *fp;

int j = 0;

size_t i;

fp = fopen(filename, "rb");

if (fp == NULL) {

printf("error\n");

return -1;

} else {

while (1) {

i = fread(&buf[j], 1, 1, fp);

if (i == 0)

break;

j ;

}

}

fflush(fp);

fclose(fp);

return 111;

}

int main(int argc, char **argv)

{

char msg[128] = "hello,xiong feng!";

char fbuf[1024] = "";

char *filename = "/xiongfeng";

int w_ret = 0;

int r_ret = 0;

w_ret = writefile(filename, msg, strlen(msg));

if (w_ret < 0) {

printf("Write file %s fail!\n", filename);

}

printf("msg:%s\n", msg);    // 如果在未加入fflush(fp), 该输出有可能不能正常输出,特别是在perl调用该程序时, perl不能获取该程序中的输出

r_ret = readfile(filename, fbuf);

if (r_ret < 0) {

printf("Read file %s fail!\n", filename);

}

printf("fbuf:%s\n", fbuf);   // 这行同上面的: printf("msg:%s\n", msg);    

return 0;

}

--------------------------------end-----------------------------------

我在网上还找了一些个封装了一次fflush的函数, 如下:

void flush(FILE *stream)

{

int duphandle;

/* flush the stream's internal buffer */

fflush(stream);

/* make a duplicate file handle */

duphandle = dup(fileno(stream));

/* close the duplicate handle to flush/

the DOS buffer */

close(duphandle);

}1、C99 对 fflush 函数的定义:

int fflush(FILE *stream);

如果stream指向输出流或者更新流(update stream),并且这个更新流最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream指向的文件(如标准输出文件stdout)。否则,fflush函数的行为是不确定的。

fflush(NULL)清空所有输出流和上面提到的更新流。如果发生写错误,fflush函数会给那些流打上错误标记,并且返回EOF,否则返回0。

由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin) 是不正确的,至少是移植性不好的。

,成功返回0,出错返回EOF。

2、使用注意点及误区

为什么 fflush(stdin) 是错的?

首先请看以下程序:

Quote:

程序代码 程序代码

#i nclude

int main( void )

{

int i;

for (;;) {

fputs("Please input an integer: ", stdout);

scanf("%d", &i);

printf("%d/n", i);

}

return 0;

}

这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为  scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。

也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(gcc3.2不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着 fflush on input stream is an extension to the C standard (fflush 操作输入流是对C标准的扩充)。当然,如果你毫不在乎程序的移植性,用  fflush(stdin) 也没什么大问题。

2.      清空输入缓冲区的方法

虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。

Quote:程序代码 程序代码

/* C 版本 */

#i nclude

int main( void )

{

int i, c;

for (;;) {

fputs("Please input an integer: ", stdout);

if ( scanf("%d", &i) != EOF ) { /* 如果用户输入的不是 EOF */

/* while循环会把输入缓冲中的残留字符清空 */

/* 读者可以根据需要把它改成宏或者内联函数 */

/* 注:C99中也定义了内联函数,gcc3.2支持 */

while ( (c=getchar()) != '/n' && c != EOF ) {

;

} /* end of while */

}

printf("%d/n", i);

}

return 0;

}

运行结果:

当输入数字时,正常;

当输入字母时,i的值不改变,printf输出的仍是上一次的值。由于输入缓冲区每次都被清空,所以也就不存在scanf自动不停读取缓冲区的值的情况。

#include  

#include  

void   main()

{

int   n   =   5;

int   i   ;

char   c[5];

for(i   =   0;   i   <   n;   i++)

{

c[i]=scanf( "   %c ",&c[i]);

/*   fflush(stdin);*/

/*printf( "%c\n ",c[i])   ;*/

}

for(i=0;i<n;i++)  ="" printf(="" "%d="" ",c[i]);=""

getch();

}

我们在运行程序的时候,输入如下

a

b

c

程序便宣告结束,而实际上程序的循环有五次,我们应该输入5次程序才会结束的,这是什么原因呢?我们先看看c语言中的输入缓冲:C语言编译器I/O输入函数一般采用了缓冲式输入方式。也就是说,当你输入“a回车b回车c回车”时,程序先输入a但同时将回车缓冲区里,然后下次再从缓冲区里取出数据,也就是回车。依次类推。可以用getchar()解决问题。根据上面的介绍,   我们再来分析我们的输入过程:

a   回车

b回车

c

恰好从缓冲区内读取了5次,因此我们的程序宣告结束了。有什么方法解决这个问题呢?

1、在每次读取之后,将缓冲区刷新——使用fflush(stdin)函数;

2、scanf( "%c ",&c[i])   ;   改成scanf( "   %c ",&c[i])   ;注意在 "%c "前面加上一个空格;

3、完全杜绝这种情况发生,使用gets()函数,或者scanf( "%s ",c)   ;一次性作为一个字符串读入;

下面再介绍一下getch,getche(),getchar();的区别,这几个函数的使用实在是太多了,这三个函数的区别一直都不清楚,今天终于有机会弄明白了:

getch()和getche()函数  

这两个函数都是从键盘上读入一个字符。其调用格式为:  

getch();  

getche();  

两者的区别是:getch()函数不将读入的字符回显在显示屏幕上,而getche()   函数却将读入的字符回显到显示屏幕上。  

getchar()函数  

getchar()函数也是从键盘上读入一个字符,   并带回显。它与前面两个函数的区别在于:   getchar()函数等待输入直到按回车才结束,     回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。

/------------------------------------------------------------------------------------------------------------------------------------------------------/

这个写的很到位:http://learn.akae.cn/media/ch25s02.html

2.10. C标准库的I/O缓冲区 请点评

用户程序调用C标准I/O库函数读写文件或设备,而这些库函数要通过系统调用把读写请求传给内核(以后我们会看到与I/O相关的系统调用),最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc/fputc为例,当用户程序第一次调用fgetc读一个字节时,fgetc函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc函数会再次进入内核读1K字节到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在第 5 节 “Memory Hierarchy”中CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。

下图以fgets/fputs示意了I/O缓冲区的作用,使用fgets/fputs函数时在用户程序中也需要分配缓冲区(图中的buf1和buf2),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。

图 25.1. C标准库的I/O缓冲区

C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时,不同类型的缓冲区具有不同的特性。全缓冲

如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。行缓冲

如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。无缓冲

用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。

下面通过一个简单的例子证明标准输出对应终端设备时是行缓冲的。#include

int main()

{

printf("hello world");

while(1);

return 0;

}

运行这个程序,会发现hello world并没有打印到屏幕上。用Ctrl-C终止它,去掉程序中的while(1);语句再试一次:$ ./a.out

hello world$

hello world被打印到屏幕上,后面直接跟Shell提示符,中间没有换行。

我们知道main函数被启动代码这样调用:exit(main(argc, argv));。main函数return时启动代码会调用exit,exit函数首先关闭所有尚未关闭的FILE *指针(关闭之前要做Flush操作),然后通过_exit系统调用进入内核退出当前进程[35]。

在上面的例子中,由于标准输出是行缓冲的,printf("hello world");打印的字符串中没有换行符,所以只把字符串写到标准输出的I/O缓冲区中而没有写回内核(写到终端设备),如果敲Ctrl-C,进程是异常终止的,并没有调用exit,也就没有机会Flush I/O缓冲区,因此字符串最终没有打印到屏幕上。如果把打印语句改成printf("hello world\n");,有换行符,就会立刻写到终端设备,或者如果把while(1);去掉也可以写到终端设备,因为程序退出时会调用exitFlush所有I/O缓冲区。在本书的其它例子中,printf打印的字符串末尾都有换行符,以保证字符串在printf调用结束时就写到终端设备。

我们再做个实验,在程序中直接调用_exit退出。#include

#include

int main()

{

printf("hello world");

_exit(0);

}

结果也不会把字符串打印到屏幕上,如果把_exit调用改成exit就可以打印到屏幕上。

除了写满缓冲区、写入换行符之外,行缓冲还有两种情况会自动做Flush操作。如果:

用户程序调用库函数从无缓冲的文件中读取

或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据

那么在读取之前会自动Flush所有行缓冲。例如:#include

#include

int main()

{

char buf[20];

printf("Please input a line: ");

fgets(buf, 20, stdin);

return 0;

}

虽然调用printf并不会把字符串写到设备,但紧接着调用fgets读一个行缓冲的文件(标准输入),在读取之前会自动Flush所有行缓冲,包括标准输出。//对于交互式行缓冲来说可以自己flush,但是其他的不一定,所以小心使用自动flush的功能,必要时自己用fflush操作

如果用户程序不想完全依赖于自动的Flush操作,可以调fflush函数手动做Flush操作。#include

int fflush(FILE *stream);

返回值:成功返回0,出错返回EOF并设置errno

对前面的例子再稍加改动:#include

int main()

{

printf("hello world");

fflush(stdout);

while(1);

}

虽然字符串中没有换行,但用户程序调用fflush强制写回内核,因此也能在屏幕上打印出字符串。fflush函数用于确保数据写回了内核,以免进程异常终止时丢失数据。作为一个特例,调用fflush(NULL)可以对所有打开文件的I/O缓冲区做Flush操作。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
c语言输入输出缓冲区的概念_仙剑系列
[zz]scanf()函数释疑
浅谈无缓存I/O操作和标准I/O文件操作区别
C语言fflush()函数
c语言中fflush(stdin)作用(转)
C语言scanf函数详细解释
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服