web-dev-qa-db-ja.com

glibcのfclose(NULL)がエラーを返す代わりにセグメンテーション違反を引き起こすのはなぜですか?

Manページfclose(3)によると:

戻り値

正常に完了すると、0が返されます。それ以外の場合は、EOFが返され、グローバル変数errnoがエラーを示すように設定されます。いずれの場合も、ストリームへのそれ以上のアクセス(fclose()への別の呼び出しを含む)は、未定義の動作になります。

エラー

EBADFfpの基礎となるファイル記述子が無効です。

fclose()関数も失敗し、ルーチンclose(2)write(2)、またはfflush(3)に指定されたエラーのいずれかにerrnoを設定する場合があります。

もちろん、fclose(NULL)は失敗するはずですが、セグメンテーション違反で直接死ぬのではなく、通常はerrnoで戻ると思います。この動作の理由はありますか?

前もって感謝します。

更新:ここにコードを配置します(特にstrerror()を試しています)。

FILE *not_exist = NULL;

not_exist = fopen("nonexist", "r");
if(not_exist == NULL){
    printError(errno);
}

if(fclose(not_exist) == EOF){
    printError(errno);
}
19
Vdragon

fcloseは、その引数として、FILE、標準ストリームfopenstdin、またはstdoutのいずれか、またはその他の実装定義の方法で取得されたstderrポインターを必要とします。ヌルポインタはこれらの1つではないため、fclose((FILE *)0xdeadbeef)のように、動作は未定義です。 NULLは特別ではありません Cで;有効なポインタと等しくないことを比較することが保証されているという事実を除けば、他の無効なポインタと同じであり、それを使用すると、NULLが持つコントラクトの一部としてドキュメントに渡すインターフェイスを除いて、未定義の動作が呼び出されますそれには特別な意味があります。

さらに、エラーを返すことは有効ですが(動作はとにかく未定義であるため)、実装にとって有害な動作です。これは、未定義の動作を非表示にするためです。未定義の動作を呼び出すことの望ましい結果は、エラーを強調表示して修正できるため、常にクラッシュです。 fcloseのほとんどのユーザーはエラーの戻り値をチェックしません。また、ほとんどの人がNULLfcloseに渡すほど愚かで、fcloseの戻り値をチェックするほど賢くはないだろうと思います。最終フラッシュが失敗する可能性があるため、人々shouldは一般にfcloseの戻り値をチェックするという議論をすることができますが、これは次の目的でのみ開かれるファイルには必要ありません。読み取り、またはfflushfcloseの前に手動で呼び出された場合(ファイルを開いたままでエラーを処理する方が簡単なので、とにかく賢いイディオムです)。

26
R..

fclose(NULL)should成功します。 free(NULL)は成功します。これにより、クリーンアップコードの記述が容易になります。

残念ながら、それはそれが定義された方法ではありません。したがって、ポータブルプログラムでfclose(NULL)を使用することはできません。 (例: http://pubs.opengroup.org/onlinepubs/9699919799/ を参照)。

他の人が述べているように、間違った場所にNULLを渡しても、通常はエラーが返されることは望ましくありません。少なくともデバッグ/テストビルドでは、警告メッセージが必要です。 NULLを逆参照すると、即時の警告メッセージが表示され、プログラミングエラーを識別するバックトレースを収集する機会が得られます:)。あなたがプログラミングしている間、セグメンテーション違反はあなたが得ることができる最高のエラーについてです。 Cにはさらに多くの微妙なエラーがあり、デバッグにはるかに時間がかかります...

エラーリターンを悪用して、プログラミングエラーに対する堅牢性を高めることができます。ただし、ソフトウェアのクラッシュによってデータが失われることが心配な場合は、まったく同じことが発生する可能性があることに注意してください。ハードウェアの電源が切れた場合。これが自動保存がある理由です( exとviのような2文字の名前を持つUnixテキストエディタ )。一貫性のない状態で続行するよりも、ソフトウェアが目に見えてクラッシュする方が望ましいでしょう。

7
sourcejedi

マニュアルページが話しているエラーは、プログラミングエラーではなく、ランタイムエラーです。ポインタを期待しているAPIにNULLを渡すだけで、そのAPIが合理的なことをすることを期待することはできません。データへのポインタを要求するように文書化された関数にNULLポインタを渡すことはバグです。

関連する質問: CまたはC++のいずれかで、ポインターパラメーターをNULL/nullptrに対してチェックする必要がありますか?

引用するには R。のコメント その質問への回答の1つについて:

...オペレーティング環境の例外的な条件(fsがいっぱい、メモリ不足、ネットワークのダウンなど)から発生するエラーとプログラミングエラーを混同しているようです。前者の場合、もちろん、堅牢なプログラムがそれらを適切に処理できる必要があります。後者の場合、堅牢なプログラムではそもそもそれらを体験できません。

4
Billy ONeal

このfclose()の問題は、FreeBSDの遺産のようであり、MicrosoftとLinuxの両方の陣営によって無批判に受け入れられました。

ただし、HP、SGI、Solaris、およびCYGWINは、すべてfclose(NULL)を適切に処理します。例えば、 man fclose OPのglibcではなくnewlibを使用するCYGWINの場合、次のように述べています。

  fclose returns 0 if successful (including when FP is NULL or not an open file)

関連する議論については https://stackoverflow.com/a/8442421/318716 を参照してください。

1
Joseph Quinsey

マンページは、基礎となるファイル記述子openを呼び出したときにfopenシステムコールを介して内部的に取得されるもの)が無効であることについて説明していると思います。 ファイルポインタこれをfcloseに渡します。

1
szx