Chinaunix首页 | 论坛 | 博客
  • 博客访问: 827309
  • 博文数量: 143
  • 博客积分: 455
  • 博客等级: 一等列兵
  • 技术积分: 861
  • 用 户 组: 普通用户
  • 注册时间: 2012-08-03 00:11
文章分类

全部博文(143)

文章存档

2018年(10)

2017年(6)

2016年(28)

2015年(14)

2014年(67)

2013年(1)

2012年(17)

我的朋友

分类: C/C++

2012-09-18 11:34:51

#include
int fflush( FILE *stream );

如果给出的文件流是一个输出流,那么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         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()函数等待输入直到按回车才结束,     回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。


/------------------------------------------------------------------------------------------------------------------------------------------------------/
这个写的很到位:
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标准库和内核之间的关系就像在中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系统调用进入内核退出当前进程[]

在上面的例子中,由于标准输出是行缓冲的,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操作。


阅读(2594) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~