そのため、入力バッファをクリアする方法を Google で簡単に検索するfflush(stdin)
と、その使用を警告する多数の Web サイトが表示されます。それでも、それはまさに私のコンピューターサイエンスの教授がクラスでそれを行うように教えた方法です.
を使用するのはどのくらい悪いですfflush(stdin)
か? 私の教授が使っていて、問題なく動作しているように見えても、本当に使うのを控えるべきでしょうか?
シンプル:fflush
出力ストリームで呼び出されることを意図しているため、これは未定義の動作です。これは C 標準からの抜粋です。
int fflush(FILE *ostream);
ostream が、最新の操作が入力されていない出力ストリームまたは更新ストリームを指している場合、fflush 関数により、そのストリームの未書き込みデータがホスト環境に配信され、ファイルに書き込まれます。それ以外の場合、動作は未定義です。
ですから、これが「どれほど悪いか」という問題ではありません。fflush(stdin)
は単純に移植性がないため、コンパイラ間でコードを移植できるようにする場合は使用しないでください。
コメントを回答に変換します。
fflush(stdin)
この回答の残りの部分では、ポータブル コードが を使用しない理由を説明しますfflush(stdin)
。「信頼できるコードは使用しない」と付け加えたくなりますがfflush(stdin)
、これも一般的に当てはまります。
fflush(stdin)
は未定義の動作のままのPOSIX、C、および C++ 標準でfflush()
は、動作が未定義であると明示的に述べられていますが (stdin
入力ストリームであるため)、システムがそれを定義することを妨げるものはありません。
ISO/IEC 9899:2011 — C11 標準 — は次のように述べています。
§7.21.5.2 fflush 関数
¶2
stream
最新の操作が入力されていない出力ストリームまたは更新ストリームを指している場合、fflush
関数は、そのストリームの未書き込みデータをホスト環境に配信してファイルに書き込みます。それ以外の場合、動作は未定義です。
POSIX はほとんど C 標準に従いますが、このテキストを C 拡張としてマークします。
[CX] ⌦ 読み取り用に開いているストリームの場合、ファイルがまだ EOF になっておらず、ファイルがシーク可能なファイルである場合、下にある開いているファイル記述のファイル オフセットがストリームのファイル位置に設定され、によってストリームに戻された文字、
ungetc()
またはungetwc()
その後ストリームから読み取られなかった文字は破棄されます (ファイル オフセットをさらに変更することはありません)。⌫</p>
端末はシークできないことに注意してください。パイプでもソケットでもありません。
fflush(stdin)
2015 年に、Microsoftと Visual Studio ランタイムは、fflush()
次のような入力ストリームでの動作を定義するために使用されました (ただし、リンクは 2021 年には別のテキストにつながります)。
ストリームが入力用に開いている場合、
fflush
バッファの内容をクリアします。
fflush(stdin)
Cygwin は、入力をクリアしないかなり一般的なプラットフォームの例です。
これが、私のコメントのこの回答バージョンに「Microsoft と Visual Studio ランタイム」と記されている理由です。Microsoft 以外の C ランタイム ライブラリを使用する場合、表示される動作はそのライブラリによって異なります。
Weather Vanefflush()
は、2021 年 6 月より前のある時点で、この回答が 2015 年に書かれたときに最初に指定されたものと比較して、Microsoft がその説明を変更したという別の質問へのコメントで私に指摘しました。
ストリームが読み取りモードで開かれた場合、またはストリームにバッファがない場合、 への呼び出し
fflush
は効果がなく、すべてのバッファが保持されます。への呼び出しは、ストリームに対するfflush
以前の への呼び出しの効果を無効にします。ungetc
講師の警告:fflush(stdin)
どのプラットフォームにも依存しないことがおそらく最善
驚くべきことに、Linuxは名目上fflush(stdin)
も の動作を文書化しており、同じように定義しています (奇跡の中の奇跡)。この引用は2015年のものです。
入力ストリームの場合
fflush()
、基になるファイルからフェッチされたが、アプリケーションによって消費されていないバッファリングされたデータを破棄します。
2021 年に、引用は次のように変更されます。
入力ストリームの場合
fflush()
、基になるファイルからフェッチされたが、アプリケーションによって消費されていないバッファリングされたデータを破棄します。ストリームのオープン ステータスは影響を受けません。
また、Linux の別の情報源もfflush(3)
同意しています (段落の区切りを入れるか取る):
シーク可能なファイル (パイプやターミナルを除くディスク ファイルなど) に関連付けられた入力ストリームの場合
fflush()
、基になるファイルからフェッチされたが、アプリケーションによって消費されていないバッファリングされたデータを破棄します。
これらのいずれも、 に関する POSIX 仕様によって作成されたポイントに明示的に対処していませんungetc()
。
2021 年、zwol はLinux のドキュメントが改善されたとコメントしました。まだまだ改善の余地がありそうです。
2015 年、私は Linux のドキュメントがそれでfflush(stdin)
うまくいくと述べていることに少し当惑し、驚きました。その提案にもかかわらず、ほとんどの場合、Linux では機能しません。Ubuntu 14.04 LTS のドキュメントを確認しました。上で引用されていることを述べていますが、経験的には機能しません—少なくとも入力ストリームが端末などのシーク不可能なデバイスである場合。
demo-fflush.c
#include <stdio.h>
int main(void)
{
int c;
if ((c = getchar()) != EOF)
{
printf("Got %c; enter some new data\n", c);
fflush(stdin);
}
if ((c = getchar()) != EOF)
printf("Got %c\n", c);
return 0;
}
$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$
この出力は、Ubuntu 14.04 LTS と Mac OS X 10.11.2 の両方で取得されました。私の理解では、Linux マニュアルの内容と矛盾しています。操作が成功した場合、2 番目に読み取るfflush(stdin)
情報を取得するために、新しいテキスト行を入力する必要があります。getchar()
POSIX 標準の内容を考えると、より良いデモンストレーションが必要になる可能性があり、Linux のドキュメントを明確にする必要があります。
demo-fflush2.c
#include <stdio.h>
int main(void)
{
int c;
if ((c = getchar()) != EOF)
{
printf("Got %c\n", c);
ungetc('B', stdin);
ungetc('Z', stdin);
if ((c = getchar()) == EOF)
{
fprintf(stderr, "Huh?!\n");
return 1;
}
printf("Got %c after ungetc()\n", c);
fflush(stdin);
}
if ((c = getchar()) != EOF)
printf("Got %c\n", c);
return 0;
}
/etc/passwd
はシーク可能なファイルであることに注意してください。Ubuntu では、最初の行は次のようになります。
root:x:0:0:root:/root:/bin/bash
Mac OS X では、最初の 4 行は次のようになります。
##
# User Database
#
# Note that this file is consulted directly only when the system is running
つまり、Mac OS X/etc/passwd
ファイルの先頭に解説があります。コメント以外の行は通常のレイアウトに準拠しているため、root
エントリは次のようになります。
root:*:0:0:System Administrator:/var/root:/bin/sh
Ubuntu 14.04 LTS:
$ ./demo-fflush2 < /etc/passwd
Got r
Got Z after ungetc()
Got o
$ ./demo-fflush2
Allotrope
Got A
Got Z after ungetc()
Got B
$
Mac OS X 10.11.2:
$ ./demo-fflush2 < /etc/passwd
Got #
Got Z after ungetc()
Got B
$
Mac OS X の動作は無視します (または、少なくとも無視しているように見えます) fflush(stdin)
(したがって、この問題では POSIX に従っていません)。Linux の動作は、文書化されている POSIX の動作に対応していますが、POSIX 仕様はその内容がはるかに慎重です。つまり、シーク可能なファイルが指定されていますが、端末はもちろんシークをサポートしていません。また、Microsoft の仕様よりもはるかに有用性が低くなります。
Microsoft は の動作を文書化していますがfflush(stdin)
、その動作は 2015 年から 2021 年の間に変更されています。どうやら、ネイティブ Windows コンパイラと C ランタイム サポート ライブラリを使用して、Windows プラットフォームで文書化されているとおりに動作するようです。
反対のドキュメントにもかかわらず、標準入力が端末の場合、Linux では動作しませんが、はるかに慎重に表現されている POSIX 仕様に従っているようです。C 標準によると、 の動作fflush(stdin)
は未定義です。POSIX は、「入力ファイルがシーク可能でない限り」修飾子を追加しますが、端末にはありません。動作は Microsoft のものと同じではありません。
したがって、移植可能なコードは を使用しませんfflush(stdin)
。Microsoft のプラットフォームに関連付けられているコードはそれを使用することができ、期待どおりに動作する可能性がありますが、移植性の問題に注意してください。
( のようなファイル ストリームとは対照的に) ターミナル ファイル記述子から未読の情報を破棄する POSIX 標準の方法は、 Unix システムの tty 入力キューから未読のデータをフラッシュするにはどうすればよいかstdin
で説明されています。ただし、それは標準 I/O ライブラリ レベル以下で動作しています。
標準によれば、fflush
出力バッファでのみ使用でき、明らかにstdin
そうではありません。ただし、一部のfflush(stdin)
標準 C ライブラリでは、 を拡張機能として使用できます。その場合は使用できますが、移植性に影響するため、地球上の標準準拠の標準 C ライブラリを使用して同じ結果を期待することはできなくなります。
POSIXからの引用:
読み取り用に開いているストリームの場合、ファイルがまだ EOF になっておらず、ファイルがシーク可能なファイルである場合、下にある開いているファイル記述のファイル オフセットがストリームのファイル位置に設定され、すべての文字がプッシュ バックされます。その後ストリームから読み取られなかった ungetc() または ungetwc() によるストリームへの書き込みは、(ファイルオフセットをさらに変更することなく) 破棄されます。
端末はシークできないことに注意してください。