了解fflush()的必要性以及与之相关的问题

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

#include  #include  #include  #include  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()会有什么问题。

有点难以说出fflush “可能有问题”(过度?)。 根据您的目标和方法,各种事物可能成为或成为问题。 可能更好的方式来看待这个是fflush的意图。

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

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

因此,提供C库及其stdio流实现的人会在您的操作系统上执行任何适当的操作,以找到“合理优化”的块大小,并将所有输出收集到该大小的块中。 (今天的数字4096,8192,16384和65536往往是好的,但它实际上取决于操作系统,有时也取决于底层文件系统。注意“更大”并不总是“更好”:例如,一次以4千兆字节为单位的数据流传输数据可能比以64千字节的数据块执行更差。)

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

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

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

你添加的fflush越多,你就越能打败收集大块数据的最初想法。 也就是说,你正在做出权衡:大块更有效率,但是会引起其他问题,所以你做出决定:“在这里效率低下,解决比单纯效率更重要的问题”。 你叫fflush

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

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

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

根据man fflush,fflush也可用于输入流:

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

这是一个演示来说明它:

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

fflush()清空与流相关的缓冲区。 如果你让用户在非常短的时间内(毫秒)输入一些数据并将一些东西写入文件,那么写入和读取缓冲区可能会留下一些“reststuff”。 你调用fflush()然后清空所有缓冲区并强制标准输出,以确保你得到的下一个输入是用户按下的。

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