あなたが何を求めているのかは明らかではありません。
あなたの基本的な質問は、「なぜ本は私がこれを行うことができないと言っているのですか?」です。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 つの方法があります。
- 読み取りと書き込みには共有バッファーを使用し、コードを書き込み、必要に応じてコンテキストを切り替えます。これは少し複雑になり、理想よりも頻繁にバッファをフラッシュします。
- 2 つの別個のバッファーとキャッシュ スタイルのコードを使用して、一方の操作でもう一方のバッファーからのコピーや無効化が必要なタイミングを判断します。これはさらに複雑で、
FILE
オブジェクトが 2 倍のメモリを必要とします。
- 共有バッファを使用し、その間に明示的なフラッシュなしで読み取りと書き込みをインターリーブすることを許可しないでください。これは非常にシンプルで、可能な限り効率的です。
- 共有バッファーを使用し、インターリーブされた読み取りと書き込みの間で暗黙的にフラッシュします。これはほぼ同じくらい簡単で、ほぼ同じように効率的で、はるかに安全ですが、安全性以外の点でそれほど優れているわけではありません.
そのため、Unix は #3 を採用して文書化し、SUS、POSIX、C89 などがその動作を標準化しました。
「さあ、それほど効率が悪いはずがない」と言うかもしれません。Unix は 1970 年代のローエンド システム向けに設計されたものであり、実際にメリットがない限り、効率を少しでも犠牲にする価値はないという基本的な考え方を覚えておく必要があります。しかし、最も重要なことは、 stdio がand のような単純な関数だけでなく、getc
andのような単純な関数を処理する必要があることを考慮してください。これらの関数 (またはマクロ) に 5 倍遅くするものを追加すると、実際の多くの場合に大きな違いが生じるでしょう。世界のコード。putc
fscanf
fprintf
たとえば、*BSD、glibc、Darwin、MSVCRT など (そのほとんどはオープン ソース、または少なくとも商用だが共有ソース) の最新の実装を見ると、それらのほとんどは同じように機能します。安全性チェックを追加するものもいくつかありますが、通常、暗黙的にフラッシュするのではなく、インターリーブに対してエラーが発生します。結局のところ、コードが間違っている場合は、DWIM を試みるよりも、コードが間違っていることを伝える方が適切です。
たとえば、初期の Darwin (OS X) fopen
、fread
、およびfwrite
(これを選択したのは、素晴らしくシンプルであり、シンタックス カラーでありながらコピー ペースト可能な、簡単にリンクできるコードを備えているためです)。バッファからバイトをfread
コピーし、バッファがなくなったらバッファを補充するだけです。これ以上簡単なことはできません。