16

下面是使用 fflush() 的示例代码:

#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <io.h>

void flush(FILE *stream);

int main(void)
{
   FILE *stream;
   char msg[] = "This is a test";

   /* create a file */
   stream = fopen("DUMMY.FIL", "w");

   /* write some data to the file */
   fwrite(msg, strlen(msg), 1, stream);

   clrscr();
   printf("Press any key to flush DUMMY.FIL:");
   getch();

   /* flush the data to DUMMY.FIL without closing it */
   flush(stream);

   printf("\nFile was flushed, Press any key to quit:");
   getch();
   return 0;
}

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);
}

我所知道的 fflush() 是一个用于刷新输出缓冲区的库函数。我想知道使用 fflush() 的基本目的是什么,在哪里可以使用它。主要是我想知道使用 fflush() 会出现什么问题。

4

3 回答 3

53

很难说“可能有问题”(过度?)使用fflush. 根据您的目标和方法,各种事情都可能成为或成为问题。可能更好的方式来看待这就是意图fflush

首先要考虑的是fflush仅在输出流上定义。输出流将“要写入文件的内容”收集到一个大(ish)缓冲区中,然后将该缓冲区写入文件。稍后收集和写入的目的是通过两种方式提高速度/效率:

  • 在现代操作系统上,跨越用户/内核保护边界会受到一些惩罚(系统必须更改 CPU 中的一些保护信息等)。如果您进行大量操作系统级别的写入调用,则您需要为每个调用付出代价。如果您将 8192 次左右的单独写入收集到一个大缓冲区中,然后进行一次调用,则可以消除大部分开销。
  • 在许多现代操作系统上,每个操作系统写入调用都会尝试以某种方式优化文件性能,例如,通过发现您已将短文件扩展为更长的文件,最好将磁盘块从 A 点移到将磁盘指向磁盘上的 B 点,这样较长的数据可以连续放置。(在较旧的操作系统上,这是一个单独的“碎片整理”步骤,您可以手动运行。您可以将其视为现代操作系统进行动态、即时的碎片整理。)如果您要写入 500 个字节,然后再写入 200 个字节,然后是 700,以此类推,它会做很多这样的工作;但是,如果您使用 8192 字节进行一次大调用,则操作系统可以一次分配一个大块,然后将所有内容放在那里,以后不必重新进行碎片整理。

因此,提供您的 C 库及其 stdio 流实现的人会在您的操作系统上做任何适当的事情,以找到“合理最佳”的块大小,并将所有输出收集到该大小的块中。(今天,数字 4096、8192、16384 和 65536 往往是好的数字,但它确实取决于操作系统,有时也取决于底层文件系统。请注意,“更大”并不总是“更好”:例如,一次以 4 GB 的块流式传输数据的性能可能比以 64 KB 的块流式传输数据要差。)

但这会产生一个问题。假设您正在写入一个文件,例如带有日期和时间戳和消息的日志文件,并且您的代码稍后将继续写入该文件,但现在,它想暂停一段时间并让日志分析器读取日志文件的当前内容。一种选择是使用fclose关闭日志文件,然后fopen再次打开它以便稍后附加更多数据。但是,将任何挂起的日志消息推送到底层操作系统文件会更有效,但要保持文件打开。就是fflush这样。

缓冲也会产生另一个问题。假设您的代码有一些错误,它有时会崩溃,但您不确定它是否即将崩溃。假设你已经写了一些东西,并且这些数据传到底层文件系统是非常重要的。在调用可能崩溃的潜在错误代码之前,您可以调用fflush将数据推送到操作系统。(有时这对调试很有用。)

或者,假设您在一个类 Unix 系统上,并且有一个fork系统调用。此调用复制了整个用户空间(克隆了原始进程)。forkstdio 缓冲区位于用户空间中,因此克隆具有与原始进程在调用时相同的缓冲但尚未写入的数据。同样,解决问题的一种方法是fflush在执行fork. 如果一切都在 之前fork,则没有什么可复制的;新克隆将永远不会尝试写入缓冲数据,因为它不再存在。

fflush添加的 -es 越多,就越会破坏收集大量数据的原始想法。也就是说,您正在做出权衡:大块更有效,但会导致一些其他问题,因此您做出决定:“在这里效率较低,解决比单纯效率更重要的问题”。你打电话fflush

有时问题只是“调试软件”。fflush在这种情况下,您可以使用 和 之类的函数setbufsetvbuf改变 stdio 流的缓冲行为,而不是重复调用。这比添加大量调用更方便(需要更少甚至不需要代码更改 - 您可以使用标志控制设置缓冲调用)fflush,因此可以将其视为“使用问题(或过度使用)的fflush“。

于 2013-05-27T23:31:40.067 回答
2

好吧,@torek 的答案几乎是完美的,但有一点不太准确。

首先要考虑的是 fflush 仅在输出流上定义。

根据 man fflush 的说法,fflush 也可以用于输入流:

对于输出流, fflush() 通过流的底层写入函数强制写入给定输出或更新流的所有用户空间缓冲数据。 对于输入流, fflush() 丢弃任何已从底层文件获取但尚未被应用程序使用的缓冲数据。流的打开状态不受影响。因此,当在输入中使用时, fflush 只是将其丢弃。

这是一个演示来说明它:

#include<stdio.h>

#define MAXLINE 1024

int main(void) {
  char buf[MAXLINE];

  printf("prompt: ");
  while (fgets(buf, MAXLINE, stdin) != NULL)
    fflush(stdin);
    if (fputs(buf, stdout) == EOF)
      printf("output err");

  exit(0);
}
于 2015-03-02T15:17:34.877 回答
0

fflush()清空与流相关的缓冲区。例如,如果您让用户在非常短的时间跨度(毫秒)内输入一些数据并将一些内容写入文件,则写入和读取缓冲区可能会保留一些“剩余内容”。然后您调用fflush()以清空所有缓冲区并强制标准输出以确保您获得的下一个输入是用户当时按下的输入。

参考:http ://www.cplusplus.com/reference/cstdio/fflush/

于 2013-05-27T21:59:58.443 回答