サイズとカウントを引数として取るfread / fwriteの根拠は何ですか?


96

ここで、freadとfwriteがメンバーあたりのサイズをとり、単にバッファーとサイズを取得するのではなく、読み取り/書き込みされたメンバーの数を数えて返す理由について、ここで議論しました。私たちが思いつくことができる唯一の用途は、プラットフォームの配置で均等に割り切れないためにパディングされているが、この選択を保証するほど一般的ではない構造体の配列を読み書きする場合ですデザインで。

FREAD(3)

関数fread()は、streamが指すストリームから各サイズバイト長のデータのnmemb要素を読み取り、ptrで指定された場所に格納します。

関数fwrite()は、各サイズバイト長のデータのnmemb要素をstreamが指すストリームに書き込み、ptrで指定された場所から取得します。

fread()およびfwrite()は、正常に読み書きされた項目の数を返します(つまり、文字数ではありません)。エラーが発生した場合、またはファイルの終わりに達した場合、戻り値は短いアイテム数(またはゼロ)です。


10
ねえ、これは良い質問です。私はいつもそれについて疑問に思いました
ヨハネスシャウブ-litb '17年

1
このスレッドをチェックしてください:stackoverflow.com/questions/8589425/how-does-fread-really-work
Franken

回答:


22

これは、freadの実装方法に基づいています。

シングルUNIX仕様によると

オブジェクトごとに、fgetc()関数に対してサイズ呼び出しが行われ、結果が読み取られた順に、オブジェクトを正確にオーバーレイする符号なしcharの配列に格納されます。

fgetcにもこの注記があります。

fgetc()はバイトで動作するため、複数のバイトで構成される文字(または「マルチバイト文字」)を読み取るには、fgetc()を複数回呼び出す必要がある場合があります。

もちろん、これはUTF-8のような派手な可変バイト文字エンコーディングよりも古いものです。

SUSは、これは実際にはISO C文書から取られていると述べています。


72

fread(buf、1000、1、stream)とfread(buf、1、1000、stream)の違いは、最初のケースでは、ファイルが小さく、 2番目のケースでは、ファイル内のすべてが1000バイト未満で最大で取得されます。


4
本当ですが、それは物語のほんの一部を語っています。たとえば、int値の配列や構造体の配列など、読んでいるものと対比するとよいでしょう。
Jonathan Leffler

3
正当化が完了した場合、これは素晴らしい答えになります。
マットジョイナー

13

これは純粋な憶測ですが、昔(一部はまだあります)、多くのファイルシステムはハードドライブ上の単純なバイトストリームではありませんでした。

多くのファイルシステムはレコードベースであったため、このようなファイルシステムを効率的に満足させるには、項目(「レコード」)の数を指定する必要があります。これにより、fwrite / freadは、バイトストリームだけでなく、レコードとしてストレージを操作できるようになります。


1
誰かがこれを取り上げてくれてうれしいです。私はファイルシステムの仕様とFTPで多くの作業を行い、レコード/ページと他のブロッキングの概念は非常にしっかりとサポートされていますが、仕様のこれらの部分はもう使用されていません。
マットジョイナー、

9

ここで、これらの関数を修正しましょう。

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

fread()/ へのパラメーターの根拠についてはfwrite()、K&Rのコピーをずっと前に失ってしまったため、推測することしかできません。KernighanとRitchieは、バイナリI / Oの実行はオブジェクトの配列に対して最も自然に行われるだろうと単純に考えていた可能性が高いと考えられます。また、一部のアーキテクチャでは、ブロックI / Oの方が実装が高速/簡単であると考えているかもしれません。

C標準のように指定していてもfread()fwrite()の観点で実装するfgetc()とはfputc()、標準のCはK&Rによって定義され、物事はオリジナルのデザイナーのアイデアにされているではないかもしれない、標準で指定されたことをされたずっと後に生まれたことを覚えておいてください。K&Rの「Cプログラミング言語」で述べられていることが、言語が最初に設計されたときと同じではない可能性さえあります。

最後に、PJ Plaugerがfread()「標準Cライブラリ」で言っていることは次のとおりです。

場合size(第2)の引数が1より大きい場合、あなたは機能もまで読んでいるかどうか判断できないsize - 1ことが報告されものを超えて追加の文字。原則として、関数を呼び出すfread(buf, 1, size * n, stream);代わりに fread(buf, size, n, stream);

基本的に、彼fread()はのインターフェースが壊れていると言っています。以下のためにfwrite()私は同意しないだろう声明-彼はそれを指摘し、「これは大きな欠点ではないので、書き込みエラーは、一般的に稀です」。


17
実際、私は別の方法でそれを行うのが好きです。fread(buf, size*n, 1, stream);不完全な読み取りがエラー条件freadである場合、読み取るバイト数ではなく、単に0または1を返すように配置する方が簡単です。次にif (!fread(...))、結果を要求されたバイト数(追加のCコードと追加のマシンコードが必要)と比較する代わりに、次のようなことを行うことができます。
R .. GitHub ICE HELPING ICEの停止

1
@R ..!fread(...)に加えて、必ずサイズ*カウント!= 0を確認してください。size * count == 0の場合、読み取りが成功すると(0バイトの)戻り値がゼロになり、feof()とferror()は設定されず、errnoはENOENTのような無意味なものになります。 、EAGAINのような誤解を招くような(そして場合によっては重大な問題が発生する)もの-非常に混乱します。特に、基本的にこのドキュメントがあなたを悩ませているドキュメントはないためです。
ペガサスイプシロン


1

サイズとカウントの引数を別々にすると、部分的なレコードの読み取りを回避できる実装に有利になる場合があります。パイプのようなものからシングルバイトの読み取りを使用する場合、固定形式のデータを使用していても、レコードが2つの読み取りに分割される可能性を考慮に入れる必要があります。代わりに、たとえば、利用可能な293バイトがある場合に、それぞれ10バイトの最大40レコードの非ブロッキング読み取りを要求し、システムに290バイト(29レコード全体)を返させながら、3バイトを次の読み取りのために準備しておくと、はるかに便利になります。

freadの実装がこのようなセマンティクスをどの程度処理できるかはわかりませんが、それらをサポートすると約束できる実装では確かに便利です。


@PegasusEpsilon:たとえば、プログラムが実行fread(buffer, 10000, 2, stdin)し、ユーザーが18,000バイトを入力した後にnewline -ctrl-Dを入力した場合、関数が最初の10,000バイトを返し、残りの8,000を将来のより小さな読み取り要求のために保留のままにしておくとよいでしょう。それが発生する実装はありますか?これらの将来の要求を保留するために、8,000バイトはどこに保存されますか?
スーパーキャット

テストしたところ、fread()はこの点で最も便利な方法で動作しないことがわかりましたが、短い読み取りを決定した後でバイトを読み取りバッファに戻すのは、おそらく予想より少し多いはずです。とにかく標準ライブラリ関数。fread()は部分的なレコードを読み取り、それらをバッファーに押し込みますが、戻り値は読み取られた完全なレコードの数を指定し、stdinから引き出された短い読み取りについては何も通知しません(かなり不快です)。
ペガサスイプシロン

...続き...できる最善の方法は、freadの前に読み取りバッファをnullで埋め、fread()がnull以外のバイトについて終了したと記録した後にレコードを確認することです。レコードにnullが含まれている可能性がある場合は特に役に立ちませんがsize、1より大きい値を使用する場合は、まあ...レコードの場合は、ioctlまたはその他のナンセンスがストリームに適用され、それを作成することもできます振る舞いが違うので、深く掘り下げていません。
ペガサスイプシロン

また、不正確だったため、以前のコメントを削除しました。しかたがない。
ペガサスイプシロン

@PegasusEpsilon:Cは非常に多くのプラットフォームで使用され、さまざまな動作に対応しています。プログラマーがすべての実装で同じ機能と保証を使用することを期待するべきであるという概念は、Cの最良の機能であったものを無視します。その設計により、プログラマーは機能が利用可能なプラットフォームで機能と保証を使用できるようになります。一部の種類のストリームは、任意のサイズのプッシュバックを簡単にサポートできfreadます。そのようなストリームで説明したように機能することは、そのように機能するストリームを特定する方法がある場合に役立ちます。
スーパーキャット

0

Cには関数のオーバーロードが欠けているためだと思います。いくつかある場合、サイズは冗長になります。しかし、Cでは配列要素のサイズを決定できないため、サイズを指定する必要があります。

このことを考慮:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

fwriteが受け入れたバイト数の場合、次のように書くことができます。

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

しかし、それは単に非効率的です。sizeof(int)倍のシステムコールが発生します。

考慮すべきもう1つのポイントは、通常、配列要素の一部をファイルに書き込まないようにすることです。あなたは整数全体か何も欲しくない。fwriteは、正常に書き込まれたいくつかの要素を返します。要素の下位2バイトのみが書き込まれていることを発見した場合、どうしますか?

一部のシステムでは(配置のため)、コピーを作成してシフトしないと整数の1バイトにアクセスできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.