web-dev-qa-db-ja.com

fflush()の必要性とそれに関連する問題を理解する

以下は、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()を使用するとどのような問題が発生する可能性がありますか

13
karan

fflushの「問題が発生する可能性がある」(過度の)可能性を説明するのは少し難しいです。あらゆる種類のことできる目標やアプローチに応じて、問題になる、または問題になる。おそらくこれをよりよく見る方法は、fflushの意図です。

最初に考慮すべきことは、fflushが出力ストリームでのみ定義されていることです。出力ストリームは、「ファイルに書き込むもの」を大きな(ish)バッファーに収集し、そのバッファーをファイルに書き込みます。この収集と書き込みのポイントは、次の2つの方法で速度と効率を向上させることです。

  • 最近のOSでは、ユーザーとカーネルの保護境界を越えるためのペナルティがあります(システムはCPUの保護情報を変更する必要があるなど)。 OSレベルの書き込み呼び出しを多数行う場合、それぞれにそのペナルティを支払います。たとえば、8192程度の個別の書き込みを1つの大きなバッファーにまとめてから1つの呼び出しを行うと、そのオーバーヘッドのほとんどが削除されます。
  • 最近の多くのOSでは、各OS書き込み呼び出しは、たとえば、短いファイルを長いファイルに拡張していることを発見するなど、何らかの方法でファイルのパフォーマンスを最適化しようとします。ディスクブロックをポイントAから移動するとよいでしょう。より長いデータが連続して収まるように、ディスクをディスク上のポイントBに移動します。 (古いOSでは、これは手動で実行する可能性のある別個の「デフラグ」ステップです。これは、動的な瞬間的なデフラグを実行する最新のOSと考えることができます。)たとえば、500バイト、さらに200バイトを書き込んだ場合、そして700など、この作業の多くを実行します。しかし、たとえば8192バイトで1つの大きな呼び出しを行うと、OSは大きなブロックを一度割り当て、そこにすべてを配置できるため、後で再デフラグする必要がありません。

したがって、Cライブラリとそのstdioストリーム実装を提供する人々は、OSで適切なことを何でも行って、「合理的に最適な」ブロックサイズを見つけ、すべての出力をそのサイズのチャンクにまとめます。 (今日、4096、8192、16384、および65536は、多くの場合、適切な値になる傾向がありますが、実際にはOSに依存し、場合によっては基礎となるファイルシステムにも依存します。「大きい」が必ずしも「良い」とは限らないことに注意してください。一度に4ギガバイトのチャンクでデータをストリーミングすると、たとえば64キロバイトのチャンクでストリーミングするよりもパフォーマンスが低下する可能性があります。

しかし、これは問題を引き起こします。日付と時刻のスタンプとメッセージを含むログファイルなどのファイルに書き込んでいて、コードが後でそのファイルへの書き込みを続けるとしますが、今はしばらく一時停止して、ログアナライザーは、ログファイルの現在の内容を読み取ります。 1つのオプションは、fcloseを使用してログファイルを閉じてから、fopenを使用して再度開き、後でデータを追加することです。ただし、保留中のログメッセージを基盤となるOSファイルにプッシュし、ファイルは開いたままにしておく方が効率的です。それがfflushが行うことです。

バッファリングも別の問題を引き起こします。あなたのコードにいくつかのバグがあり、時々クラッシュしますが、それがクラッシュするかどうかわかりません。そして、あなたが何かを書いて、thisデータが基礎となるファイルシステムに出て行くことが非常に重要だとしましょう。 fflushを呼び出して、クラッシュする可能性のある不良コードを呼び出す前に、データをOSにプッシュすることができます。 (これはデバッグに適している場合があります。)

または、Unixライクなシステムを使用していて、forkシステムコールがあるとします。この呼び出しは、ユーザー空間全体を複製します(元のプロセスのクローンを作成します)。 stdioバッファーはユーザー空間にあるため、クローンは、fork呼び出し時に、元のプロセスが保持していた、まだ書き込まれていない、まだ書き込まれていない同じデータを持っています。ここでも、問題を解決する1つの方法は、fflushを使用する直前にforkを使用してバッファーデータをプッシュすることです。 forkの前にすべてが出ている場合、複製するものはありません。新しいクローンはバッファリングされたデータが存在しないため、バッファリングされたデータを書き込もうとしません。

追加するfflush- esが多いほど、大きなデータのチャンクを収集するという本来のアイデアを打ち破ることになります。つまり、トレードオフを行っています。大きなチャンクはより効率的ですが、他のいくつかの問題を引き起こしているため、「ここでは効率が低く、単なる効率よりも重要な問題を解決する」という決定をします。 fflushを呼び出します。

時々問題は単に「ソフトウェアをデバッグする」ことです。その場合、fflushを繰り返し呼び出す代わりに、setbufsetvbufなどの関数を使用して、stdioストリームのバッファリング動作を変更できます。これは、多くのfflush呼び出しを追加するよりも便利です(必要なコード変更は少なく、必要なコード変更もありません-フラグでセットバッファリング呼び出しを制御できます)。そのため、「使用に関する問題」と見なすことができます。 (または過度の使用)fflush "。

47
torek

まあ、@ torekの答えはほぼ完璧ですが、それほど正確ではない点が1つあります。

最初に考慮すべきことは、fflushが出力ストリームでのみ定義されることです。

Man fflushによると、fflushはinputストリームでも使用できます。

出力ストリームの場合、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);
}
1
jaseywang