6

(キャラクターI/ O)の代わりに(ブロックI/O)をgetline使う機能はありますか?freadfgetc

を介してファイルを文字単位で読み取ると、パフォーマンスが低下しますfgetcfreadパフォーマンスを向上させるために、 の内部ループでブロック読み取り経由を使用できると考えていますgetline。ただし、これにより、行末を超えて読み取るという望ましくない影響が生じる可能性があります。少なくとも、これにはgetline、ファイルの「未読」部分を追跡するための の実装が必要です。これには、ANSI C FILE セマンティクスを超えた抽象化が必要です。これは私たちが自分たちで実装したいものではありません!

アプリケーションのプロファイリングを行いましたが、パフォーマンスの低下は、大きなファイルを 1 文字ずつ消費しているという事実に起因していますfgetc。残りのオーバーヘッドは、比較すると実際には取るに足らないコストです。ファイルのすべての行を最初から最後まで常に順番に読み取り、読み取り中はファイル全体をロックできます。これにより、おそらくfreadベースのgetline実装が容易になります。

では、(キャラクターI/ O)の代わりに(ブロックI/O)をgetline使う機能は存在するのでしょうか?そうなっていると確信していますが、そうでない場合、どのように実装すればよいのでしょうか?freadfgetc

更新Paul Hsieh による便利な記事、 Handling User Input in Cを見つけました。これは にfgetc基づくアプローチですが、代替案について興味深い議論があります (最初に がいかに悪いかgets、次に について議論fgetsます)。

一方、C プログラマー (経験者と見なされている人でも) からの一般的な反論は、代わりにfgets()を使用する必要があるというものです。もちろん、それ自体では、fgets()は実際にはユーザー入力自体を処理しません。奇妙な文字列終了条件 (\n または EOF に遭遇したとき、\0 に遭遇したとき) があることに加えて、バッファーが容量に達したときに終了するために選択されたメカニズムは、単純にfgets()操作を突然停止し、\0 で終了することです。したがって、ユーザー入力が事前に割り当てられたバッファーの長さを超える場合、fgets()部分的な結果を返します。この問題に対処するには、プログラマーにはいくつかの選択肢があります。1) 単純に切り捨てられたユーザー入力を処理します (入力を提供している間、入力が切り捨てられたことをユーザーにフィードバックする方法はありません) 2) 拡張可能な文字配列をシミュレートし、fgets()への連続呼び出しで入力します。 . 最初の解決策は、ほとんどの場合、可変長のユーザー入力に対して非常に貧弱な解決策です。これは、多くの通常のケースをキャプチャしようとするため、ほとんどの場合、バッファーが必然的に大きくなりすぎ、異常なケースでは小さすぎるためです。2 番目の解決策は、正しく実装するのが複雑になることを除けば問題ありません。どちらも'\0' に関するfgets の奇妙な動作を扱いません。

読者に残された演習: fgets()への呼び出しによって実際に読み取られたバイト数を決定するために、'\n' をスキャンするのと同じように、'\0' をスキップしてみてください。fgets()に渡されるサイズを超えない。ストリームの最後の行でこれが不十分な理由を説明してください。ftell() のどの弱点が、この問題に完全に対処するのを妨げていますか?

演習は読者にお任せします: fgets( )への呼び出しごとにゼロ以外の値でバッファ全体を上書きすることにより、 fgets()によって消費されるデータの長さを決定する問題を解決してください。

そのため、fgets()では、多くのコードを記述し、C ライブラリの残りの部分と矛盾する行終了条件に対処するか、任意のカットオフを設定するかを選択する必要があります。これで十分でない場合、何が残されるのでしょうか? scanf()は、分離できない方法で解析と読み取りを混合し、fread()は文字列の末尾を超えて読み取ります。要するに、C ライブラリには何も残されていません。fgetc()の上に直接基づいて独自にロールすることを余儀なくされています。それでは、試してみましょう。

それで、にgetline基づくfgets(そして入力を切り捨てない)関数は存在しますか?

4

2 に答える 2

5

使用しないでくださいfread。を使用しfgetsます。これは宿題/クラスプロジェクトの問題だと思うので、完全な答えは提供していませんが、そうではないという場合は、さらにアドバイスします. getline純粋に を使用して、埋め込まれた null バイトを含め、GNU スタイルのセマンティクスを 100% 提供することは間違いなく可能ですfgetsが、それにはいくつかの賢明な思考が必要です。

これは宿題ではないので、更新します。

  • memsetへのバッファ'\n'
  • を使用しfgetsます。
  • memchr最初の を見つけるために使用します'\n'
  • no'\n'が見つかった場合、その行はバッファよりも長くなっています。バッファーを拡大し、新しい部分を で埋め、新しい部分に'\n'入れfgets、必要に応じて繰り返します。
  • 続く文字'\n'がの場合は'\0'fgets行末に達したため終了しています。
  • それ以外の場合は、fgetsEOF に達したために終了し、'\n'は から残りmemset、前の文字は書き込んだ終端の null でfgetsあり、その前の文字は実際に読み取られたデータの最後の文字です。

null が埋め込まれた行をサポートすることを気にしない場合は、 を削除して代わりにmemset使用できます (どちらの方法でも、null は読み取りを終了しません。読み取り行の一部になります)。strlenmemchr

fscanfおよび"%123[^\n]"指定子 (123バッファ制限はどこにありますか) を使用して同じことを行う方法もあります。これにより、改行以外の文字 (ala GNU getdelim) で停止する柔軟性が得られます。ただし、システムに非常に手の込んだscanf実装がない限り、おそらく遅いでしょう。

于 2010-12-10T17:04:49.290 に答える
1

fgets と fgetc/setvbuf の間に大きなパフォーマンスの違いはありません。試す:

int c;
FILE *f = fopen("blah.txt","r");
setvbuf(f,NULL,_IOLBF,4096); /* !!! check other values for last parameter in your OS */
while( (c=fgetc(f))!=EOF )
{
  if( c=='\n' )
    ...
  else
    ...
} 
于 2010-12-10T22:06:23.050 に答える