none
fclose()に失敗する RRS feed

  • 質問

  • 一定周期ではありませんが、周期的に10~90程度のバイナリファイルを出力するプログラムをWindows2008r2サーバで動作させています。

    たまにですが、fclose()でエラーとなり、ファイルサイズが0のファイルが出力されてしまう場合があります。

    fclose()でエラーとなった時、GetLastError()の戻り値は「6」(ハンドルが無効)でした。

    fopen()してからfclose()するまでの間に、fwrite()でエラーになった場合にfclose()を呼び出して終了しる処理はありますが、

    fclose()を2重に呼び出すことはありません。(ログでも確認しています)

    fclose()内でハンドルが無効と判定されてしまう場合として、何が考えられるでしょうか?

    2015年3月26日 5:36

すべての返信

  • >fclose()内でハンドルが無効と判定されてしまう場合として、何が考えられるでしょうか?

    わたしたハンドル自体がおかしい場合だと考えられます。

    1.ハンドルが上書きされてしまい、他の値が入ったハンドルを渡した場合。
    2.そもそもオープンに失敗している場合(この場合ハンドルの値はNULL)。

    さて、マニュアルにある通り、fclose()のエラーはGetLastError()ではなく、errno を見なければなりません。
    (ただし、fclose()関数がWin32APIのラッパーになっている場合は同じかもしれません。)
    同じくマニュアルにある通り fclose()がエラーを検知した場合には EOF が戻ります。
    0でなくEOFでもない場合は、全体がおかしくなっているかもしれません。
    例えば、if( 0 != fclose()) のようなコードの場合はそれを検知できません。
    注意深く見てみてはどうでしょう。

    • 回答の候補に設定 星 睦美 2015年3月27日 5:44
    • 回答の候補の設定解除 星 睦美 2015年4月1日 0:37
    2015年3月26日 6:06
  • ご回答ありがとうございます。

    ソースコードを解析し、ハンドルが異常になるケースを考えてみましたが、
    特に見当たりません。

    直前でハンドルを使っているのは、fwrite()だけですので、上書きされることは
    無いと思いますし、fopen()でハンドルがNULLの場合は、関数から抜けるので
    fclose()が実行されることはありません。
    fwrite()ではエラーは発生していません。

    fclose()の場合は、errnoを見なくてはいけないというこをコロっと
    忘れていました。
    fclose()の戻り値についても、確認できるようにしてみます。

    3ヶ月連続動作させて1回だけ発生した現象ですので、またしばらく連続動作を
    させてみます。

    2015年3月31日 5:17
  • ファイルポインタ変数fptrの近傍に、なんらかのバッファや変数等があり、
    それに対してバッファ数(変数の型のサイズ)をオーバーして書き込んだりすれば
    fptrは簡単に書き換えられる可能性があります。
    こういったあたりも調べてみましたでしょうか。
    旧式のC言語コードでありがちなのは、例えば

    char     bufA[ 20]; // 何らかのバッファ
    int      variable;  // 何らかの変数
    FILE *   fptr;      // ファイルポインタ
    char     bufB[ 10]; // 何らかのバッファ

    fptr = fopen();// オープンした後
    // bufA、variable、bufB等に対する書き込み処理等
    // 例えば sprintf()等でbufA、bufBに文字列をセットしたりなどして
    // バッファーオーバーランした場合ですね
    // スタックに並べる順番の関係で、bufBがオーバーランすると即死ですね。

    ちなみに、現在のVSコンパイラでは fopen()などは旧式で危険なのでコンパイルエラーになります。

    2015年3月31日 5:58
  • ちなみに、現在のVSコンパイラでは fopen()などは旧式で危険なのでコンパイルエラーになります。

    fopen()を使用した場合に発生するC4996は警告でありエラーではありません。ただしコンパイルオプション/sdl 追加のセキュリティ チェックの有効化が指定されている場合はエラー扱いされます。

    説明としても代替策が提示されていないので片手落ちに感じました。fopen_s()を想定されているのでしょうか?
    ちなみに警告もしくはエラーとなるのは仲澤さんが説明されているような理由ではなく、fopen()は歴史的に共有モードでのオープンが認められているのに対してfopen_s()は共有モードが認められていません。そういう意味でのセキュリティ強化バージョンです。fopen_s()を用いたとしても挙げられているようなバッファーオーバーフローによる破壊には無力です。

    2015年3月31日 6:44
  • 詳しい解説をありがとうございます。

    質問者さんのビルド環境がよくわからなかったので、
    良く考えずにいい加減な回答してしまいました。

    (1)質問者さんのコードの問題点の発見を最重要事項と考えていたので、ちなみに以降は文字通り蛇足です。
    (2)C4996はVS2013のデフォルト設定だと「ビルド失敗」になりますが、警告でしたね。
    (3)ビルド環境が不明なのでfopen_s()は意図してませんでした。
    (4)同じ理由でセキュリティも共有モードについても強く意識してません。

    以上、訂正を含めて説明させていただきました。申し訳ありません。

    2015年3月31日 7:52
  • なんか、前提と仮定がごっちゃになって、論点がずれてしまっているように見えるのですが。。。

    現在事実としてわかっていることは、「ファイルサイズが0のファイルが出力されてしまう」ということだけですよね?
    「fclose()でエラーとなり」は、fclose() でエラーと判断された理由を明記されていないので、この部分は現状では前提とすべきではないと思います。
    (そもそもこの質問では、"ストリーム ポインタ" と "ファイル ハンドル" を混同されている節がありますし。)
    「ファイルサイズが0のファイルが出力されてしまう」という事実を前提として考えるのであれば、本質問では fopen() での処理を議論する必要は無いと思うのです。
    fopen() で取得したストリーム ポインタが上書きされている可能性を確認したいのであれば、fclose() での戻り値が EOF となっているのか、明確にする必要があると考えます。
    もし fclose() での戻り値が 0 ならば、fwrite() での書き込み処理に起因している可能性を調査する必要がありますし、いずれにせよ状況を適切に把握する必要があるのでは?
    いきなり仮定を前提とした推測を議論するのではなく、事実から推測される仮定を検証するための議論が必要だと感じました。



    • 編集済み お馬鹿 2015年3月31日 8:39
    2015年3月31日 8:07
  • > fclose()でエラーとなった時、GetLastError()の戻り値は「6」(ハンドルが無効)でした。

    という記述を信じる限り、fclose()の戻り値を確認する部分までは正しく、その後のエラー理由の取得方法に誤りがあっただけと読めます。

    > fwrite()ではエラーは発生していません。

    という記述を信じる限り、fwrite()の戻り値は確認されていると読めます。

    これらに0バイトファイルが作成されている点を加えると、fclose()内のfflush()に失敗しているのだろうかというところまでは推測できます。ただしfclose()の最後に呼び出した何らかのWindows APIが6(ハンドル無効)を設定してしまう状況というのはちょっとわかりませんでした。

    # ので、特に質問には答えられずにいました。ロジックに誤りがあり、意図せずfclose()の2回呼び出しくらいかなぁ、とか…。

    2015年3月31日 8:29
  • fwriteは書き込む長さがfpのbufsizeより小さい場合は、内部のバッファへコピーするだけで帰ってきます。
    実際の書き込みはfclose時のflushが実行するので、たとえばその時点で実は空き容量がかった場合などに、
    fcloseがエラーで帰ってくることはあり得ます。

    jzkey

    2015年4月1日 2:59
  • >コメントを頂いた皆様へ

    ご検討頂きましてありがとうございます。
    色々と情報が足らずにご迷惑をおかけしました。

    ファイルポインタ変数のメモリ破壊についてですが、「FILE *fp;」は関数の1行目に記載しています。
    この場合、破壊される可能性としては何が考えられるでしょうか?

    「fclose()でエラーとなり」に関しましては、fclose()の戻り値が0以外の場合はエラーと判定し、ログを残すようにしています。
    ソースコードを以下に記載します。
    fclose()でエラーと判定する処理は★印のところになります。
    -----------------------------------------------------------------------
     // ファイルオープン
     fp = fopen( flnma, "wb+" );
     if( fp == NULL ) {
         // エラー時
         iErrCode = GetLastError();
         sprintf( cMsgBuf, "fopen()に失敗[%d]", iErrCode );
         XXXXX(cMsgBuf,ERROR_MES); ※自作のログ出力関数です
         return -1;
     }

     // ファイル書き込み処理
     fw_ret = fwrite( &data, 1, len, fp );
     if (fw_ret != 4) {
         // エラー時
         iErrCode = GetLastError();
         sprintf( cMsgBuf, "fwrite()に失敗[%d]", iErrCode );
         XXXXX(cMsgBuf,ERROR_MES); ※自作のログ出力関数です
        
         fc_ret = fclose(fp);
         if (fc_ret != 0) {
             // エラー時
             iErrCode = GetLastError();
             sprintf( cMsgBuf, "書込みエラー処理 fclose()に失敗[%d]", iErrCode );
             XXXXX(cMsgBuf,ERROR_MES); ※自作のログ出力関数です
         }
         return -1;
     }
     
     // ファイルクローズ
     fc_ret = fclose(fp);
     if (fc_ret != 0) { ★
         // エラー時
         iErrCode = GetLastError();
         sprintf( cMsgBuf, "fclose()に失敗[%d]", iErrCode );
         XXXXX(cMsgBuf,ERROR_MES); ※自作のログ出力関数です
         // リトライ実施
         return -1;
     }
    -----------------------------------------------------------------------

    現象発生時、0バイトファイルが作成されましたが、ファイルがクローズされていないようで
    ファイルを削除しようとしたところ、「プロセスが使用中」との警告が表示されました。

    2015年4月21日 5:07
  • ファイルポインタ変数のメモリ破壊についてですが、「FILE *fp;」は関数の1行目に記載しています。
    この場合、破壊される可能性としては何が考えられるでしょうか?

    よくあるのは、fp の前後に定義されているローカル変数への代入、配列への書き込みで確保されているサイズ以上に書き込んでしまい、fp の内容を書き換えてしまうことです。
    こればっかりは、現状のソースコード断片からは、あるともないとも言えません。

    2015年4月21日 13:49
    モデレータ
  • fpの内容が書き換えられている可能性があるならば、
    オープン直後の fpの内容 ( long でキャストすれば良い?) をログに出力し、
    エラーが発生した時の fpの内容 (同様にキャスト) をログに出力して、一致しているか確認すれば、良いかと思います。
    違っていれば、 fpの内容が壊されているので、その方向でデバッグかと思います。

    同じだったら、別の原因という事ですね。

    2015年4月21日 15:33
  • この程度であれば、stdioではなく、CreateFile/WriteFile/CloseHandle APIを使うように書き換えてもたいしたこと無いと思います。いちどローレベルのAPIを使ってどこで何が起こっているか確かめられるのもよろしいのでは?(GetLastErrorも正確になりますし)


    jzkey

    2015年4月21日 23:51
  • >  // ファイル書き込み処理
    >  fw_ret = fwrite( &data, 1, len, fp );
    >  if (fw_ret != 4) {
    なんで ぢゃないとだめなんですか?
    そもそも 3rd パラメータに変数 len を指定しているってことは、変数 len の取りうる値は「可変」ってことなんですか?
    さらにいえば、変数 len が になる可能性は全くないんですか?
    2015年4月22日 1:49