7

UNIX 環境での高度なプログラミング』 (第 2 版) のセクション 5.5 (標準 I/O ライブラリのストリーム操作) で、著者は次のように書いています。

ファイルが読み書き用に開かれている場合 ( typeのプラス記号)、次の制限が適用されます。

  • fflushfseekfsetpos、またはを介さずに、出力の直後に入力を続けることはできませんrewind
  • fseek、 、 、fsetpos、またはrewindファイルの終わりに遭遇する入力操作が介在しない限り、入力の直後に出力を続けることはできません。

私はこれについて混乱しました。誰かこれについて少し説明してもらえますか? たとえば、上記の制限に違反する入力および出力関数呼び出しがプログラムの予期しない動作を引き起こすのは、どのような状況ですか? 制限の理由は、ライブラリ内のバッファリングに関連している可能性があると思いますが、明確ではありません。

4

2 に答える 2

4

入力操作と出力操作を混在させることはできません。たとえば、フォーマットされた入力を使用してファイル内の特定のポイントをシークし、そのポイントから始まるバイトの書き込みを開始することはできません。これにより、実装では、安全性チェックを行わずに、いつでも唯一の I/O バッファーに (ユーザーに対して) 読み取りまたは (OS に対して) 書き込むデータのみが含まれると想定できます。

f = fopen( "myfile", "rw" ); /* open for read and write */
fscanf( f, "hello, world\n" ); /* scan past file header */
fprintf( f, "daturghhhf\n" ); /* write some data - illegal */

ただし、とのfseek( f, 0, SEEK_CUR );間でを実行する場合は問題ありません。これにより、位置を変更せずに I/O バッファーのモードが変更されるためです。fscanffprintf

なぜこのように行われるのですか?私が知る限り、OS ベンダーは自動モード切り替えをサポートしたいと考えていますが、失敗しています。このstdio仕様では、バグのある実装を準拠させることができ、自動モード切り替えの機能する実装は、互換性のある拡張機能を実装するだけです。

于 2013-01-16T08:54:14.913 に答える
3

あなたが何を求めているのかは明らかではありません。

あなたの基本的な質問は、「なぜ本は私がこれを行うことができないと言っているのですか?」です。POSIX/SUS/etc. これは、ISO C 標準( N1124ワーキング ドラフト、最終バージョンはフリーではないため) に準拠するために行われますfopen

次に、「上記の制限に違反する入力関数呼び出しと出力関数呼び出しがプログラムの予期しない動作を引き起こすのはどのような状況ですか?」と尋ねます。

未定義の動作は、常に予期しない動作を引き起こします。これは、何も期待できないことが重要だからです。(上記リンクの C 標準の 3.4.3 および 4 を参照してください。)

しかし、それに加えて、彼らが何を指定できたのか、それが意味を成すかどうかさえ明らかではありません. これを見てください:

int main(int argc, char *argv[]) {
  FILE *fp = fopen("foo", "r+");
  fseek(fp, 0, SEEK_SET);
  fwrite("foo", 1, 3, fp);
  fseek(fp, 0, SEEK_SET);
  fwrite("bar", 1, 3, fp);
  char buf[4] = { 0 };
  size_t ret = fread(buf, 1, 3, fp);
  printf("%d %s\n", (int)ret, buf);
}

では、これ3 fooがディスク上にあるため、または3 bar「概念ファイル」に0あるため、または書き込まれた後に何もないため、EOF で読んでいるために、これを出力する必要がありますか? 明らかな答えがあると思われる場合は、が既にフラッシュされbar booいる可能性があるという事実、または部分的にフラッシュされている可能性があるという事実を考慮してください。そのため、ディスク ファイルには.

「ある状況でそれを回避できますか?」というより実際的な質問をしている場合、まあ、ほとんどの Unix プラットフォームでは、上記のコードによって時折 segfault が発生すると思いますが、3 xyz(3 つの初期化されていない文字、またはより複雑なケース (上書きされる前にたまたまバッファーにあった 3 文字) 残りの時間。だから、いいえ、あなたはそれを逃れることはできません。

最後に、「制限の理由はライブラリ内のバッファリングに関連していると思われますが、よくわかりません。」これは、あなたが根拠について尋ねているように聞こえます。

おっしゃる通り、バッファリングに関するものです。上で指摘したように、ここで行うべき直感的な正しいことは実際にはありませんが、実装についても考えてください。Unix のやり方は常に「最も単純で最も効率的なコードで十分である場合は、それを実行する」というものであることを思い出してください。

stdio のようなものを実装するには、次の 3 つの方法があります。

  1. 読み取りと書き込みには共有バッファーを使用し、コードを書き込み、必要に応じてコンテキストを切り替えます。これは少し複雑になり、理想よりも頻繁にバッファをフラッシュします。
  2. 2 つの別個のバッファーとキャッシュ スタイルのコードを使用して、一方の操作でもう一方のバッファーからのコピーや無効化が必要なタイミングを判断します。これはさらに複雑で、FILEオブジェクトが 2 倍のメモリを必要とします。
  3. 共有バッファを使用し、その間に明示的なフラッシュなしで読み取りと書き込みをインターリーブすることを許可しないでください。これは非常にシンプルで、可能な限り効率的です。
  4. 共有バッファーを使用し、インターリーブされた読み取りと書き込みの間で暗黙的にフラッシュします。これはほぼ同じくらい簡単で、ほぼ同じように効率的で、はるかに安全ですが、安全性以外の点でそれほど優れているわけではありません.

そのため、Unix は #3 を採用して文書化し、SUS、POSIX、C89 などがその動作を標準化しました。

「さあ、それほど効率が悪いはずがない」と言うかもしれません。Unix は 1970 年代のローエンド システム向けに設計されたものであり、実際にメリットがない限り、効率を少しでも犠牲にする価値はないという基本的な考え方を覚えておく必要があります。しかし、最も重要なことは、 stdio がand のような単純な関数だけでなく、getcandのような単純な関数を処理する必要があることを考慮してください。これらの関数 (またはマクロ) に 5 倍遅くするものを追加すると、実際の多くの場合に大きな違いが生じるでしょう。世界のコード。putcfscanffprintf

たとえば、*BSD、glibc、Darwin、MSVCRT など (そのほとんどはオープン ソース、または少なくとも商用だが共有ソース) の最新の実装を見ると、それらのほとんどは同じように機能します。安全性チェックを追加するものもいくつかありますが、通常、暗黙的にフラッシュするのではなく、インターリーブに対してエラーが発生します。結局のところ、コードが間違っている場合は、DWIM を試みるよりも、コードが間違っていることを伝える方が適切です。

たとえば、初期の Darwin (OS X) fopenfread、およびfwrite(これを選択したのは、素晴らしくシンプルであり、シンタックス カラーでありながらコピー ペースト可能な、簡単にリンクできるコードを備えているためです)。バッファからバイトをfreadコピーし、バッファがなくなったらバッファを補充するだけです。これ以上簡単なことはできません。

于 2013-01-30T02:13:09.093 に答える