パスワードを忘れた? アカウント作成
15606405 story
バグ

Hello, Worldに潜むバグ 67

ストーリー by nagazou
Hello,-World 部門より
route127 曰く、

プログラミングの第一歩としてお馴染みの課題であるHello, Worldであるが、これをANSI-Cに基づいてmainの戻り値をEXIT_SUCCESSマクロで記述し、出力をENOSPCエラーを返す疑似デバイスファイルである/dev/nullへリダイレクトさせるよう実行すると正常終了するというバグがあるようだ。
(https://blog.sunfishcode.online/bugs-in-hello-world/)

C言語以外でもJava、Haskell、Node.js、Ruby、およびPython 2では同様の動作であるが、Python 3、Perl、およびBashでは正しくエラーとなるとのことである。
Linuxでは/dev/fullの存在はお馴染みとなっているがBSD系では2014年にFreeBSD 11.0-CURRENTが、2018年にNetBSD 8が/dev/fullを追加しているようだ。

こうしたANSI-Cに比べれば追加されて日が浅い機能である/dev/fullがバグを顕在化させた側面もあるのだろうか?
豊富な実務経験を持つスラド諸兄から本件の「バグ」についてご意見を頂戴したいところである。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • by Anonymous Coward on 2022年03月24日 16時25分 (#4220820)

    ・C言語の高水準入出力 printf(3) とか fputs(3) で出力した内容はキャッシュされるので fclose(3) とか fflush(3) とかで吐き出して戻り値を確認しないと正常に出力されたことは保証されない。
    ・exit(3) なり main() からの return で暗黙にフラッシュした場合はエラーチェックできないので、失敗してもわからない。

    というのは基本仕様なので知っとく必要がある。
    /dev/full とかだけでなく、write(2)で発生するあらゆるエラーの可能性がある。

    • by Anonymous Coward on 2022年03月24日 17時28分 (#4220869)

      C言語の仕様と離れるけど補足しておくと、書き込み先が NFS のような場合にはカーネルが遅延書き込みをするので fflush(3) が成功したとしてもまだ十分ではない。
      あとから遅延してエラーが帰ってくるかもしれないので fclose(3) の戻り値を確認するまでは安心できない。

      別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。

      親コメント
      • by Anonymous Coward

        > 別件としてカーネル内にもページキャッシュがあるので、ディスクドライブへ書き込みされたことを保証したいなら fsync() なり sync() なりを検討する必要もある。

        つまり書き込み保証を移植性のある方法で行うことは不可能

    • by Anonymous Coward on 2022年03月25日 1時53分 (#4221067)

      ちなみに、coreutilsのechoは正しくハンドルしていたのでその実装を確認してみると、
      atexit()で登録した関数close_stdout()とそこから呼ばれるclose_stream()にて、
      fclose()他のstdout周りで起こったエラーがないかをチェックしている。
      https://github.com/coreutils/gnulib/blob/master/lib/closeout.c#L117 [github.com]

      親コメント
    • by Anonymous Coward

      それはCの話なのかUnixの話なのかオーエス全般の話なのか…
      ショリケイイゾンテクソダナ

    • by Anonymous Coward

      これC言語にフォーカスしているからおかしい(のとC言語もバグ扱いにしているのがおかしい)。
      JavaとかNode.jsみたいに例外処理がある処理系でも発生しているのは確かに不思議。
      Javaは調べてみると、PrintStreamはIOExceptionを投げない [oracle.com]となっているから、そもそもそういう仕様なのね。
      # バグを仕様と言い張るのと逆で、仕様をバグと言い張っているのか

      • by Anonymous Coward

        Javaでもmainメソッドの外で暗黙に閉じられたstreamが仮に例外を投げるとしてもcatchのしようがないのでは?

        • by Anonymous Coward

          Javaも例外が拾われずに終了する場合は1(エラー)を返すので、問題はキャッシュしたstreamがflushされるタイミングですね。
          普通に考えて処理最後にはflushされるはずなので、仮にstreamが例外をthrowするならエラーで終わる気がします。
          C#はエラーになっているので、そのあたりは作りしだいですが。

          • c# (net6)は挙動とソースをみると、Console.Writen系では毎回呼び出し毎にflushしているので、呼んだ時点で例外が起きる。
            CはターミナルならLine Bufferingだが、リダイレクトされているとFull Bufferingになるのでfwriteした時点では気づかないとかの話になる。まあこれもパフォーマンスの話で気になるならsetlinebufなりsetvbuf(_IONBF)なり呼べば、というのもわかる話。
            API設計の時代背景(かWindows文化的にリダイレクトをバッファリングするとデメリットがでるのでやらない)だろうね。

            親コメント
          • by Anonymous Coward

            >普通に考えて処理最後にはflushされるはず
            これな、Streamクラスによっては仕様で担保されてなくて、まあほとんどはSun由来のJRE使うから問題ないんだけど、IBMの組み込み向けJ2MEでflushされないのをライブラリのソース読んで確認した。
            大昔の話。

            • by Anonymous Coward

              PCみたいな富豪環境以外は終了処理をリソース食わずに素早く終わらせることを優先するのが普通だよ。Cまでいくと仕様上は振る舞いを説明するだけでなんの保証もなく、ただホストに投げると明記される程度だし。ioはホスト/プラットフォーム依存の塊だし。

              本当の問題は富豪環境でもRAIIやファイナライザのコールバックがioのcloseの際のエラーを握りつぶすしかないことだよ。きれいにやりたいなら手動でやるしかない。

  • by Anonymous Coward on 2022年03月24日 16時33分 (#4220829)

    putsの実行結果戻り値をハンドリングせずにプログラムを常に正常終了するようなコードなんだから、putsが成功しようが失敗しようが正常終了するのが当たり前に思えるんですけど。

    • 全く同意
      「エラー処理してるつもりが効いてない」ならわかるけどエラー処理してないんだからなぁ
      エラーになるんならバグだけど

      親コメント
    • by Anonymous Coward on 2022年03月24日 18時30分 (#4220909)

      > putsの実行結果戻り値をハンドリングせずに

      知ったかぶってるけど、この場合 puts() の戻り値をチェックするだけでは不足なんだよ。
      やってみればすぐわかるけど、標準的な環境でファイルへの puts() は (メモリが足りてる限り) 必ず成功する。

      書き込みエラーの確認をしたい場合には上に書かれているように、その後で fflush() とか fclose() して、
      その戻り値を確認する必要がある。

      親コメント
      • by Anonymous Coward

        そこは本質じゃないと思うのですが。
        問題は「必ず正常終了するプログラムを実行したら必ず正常終了する。どこがバグ?」というところなので。
        # ま、正確には「puts()が失敗した時にプログラムを異常終了する処理なしに」と書くべきでしたね。

        • by Anonymous Coward

          元コメとは別人だけど、

          >> putsの実行結果戻り値をハンドリングせずに

          こんな風に無駄に間違った詳細を書いてる以上、ツッコまれるのは当然で、「そこは本質じゃない」はゴマカシに見えるかな。

        • by Anonymous Coward

          > 正確には「puts()が失敗した時にプログラムを異常終了する処理なしに」と書くべきでしたね。

          やっぱりわかってないよね?
          今回の /dev/full にリダイレクトする条件では puts("Hello World") は失敗しないので、「puts() が失敗した時にプログラムが異常終了する処理」を記述したとしても違いはないよ。

          • by Anonymous Coward

            そもそもputsが成功しているかすらチェックしていない、すなわち必ず書き込まれることを期待していないのだから
            書き込みに失敗していようが正常終了だろって意味では

            • by Anonymous Coward

              いや、だから、今回の例では puts() は、確実に成功するので戻り値を確認する意味なんて元々ない。
              書き込まれたか確認したいなら、その後で fclose(stdout) して、その戻り値を確認するべき。(それで必要十分)。

              • by Anonymous Coward

                それがputs()が失敗したかチェックするという事ではないのですか?

    • by Anonymous Coward

      「俺の思う最強のhelloworldの仕様を満たしてないから、この実装はバグってる!」って主張してるだけだよな。
      Bashはバグってないとか言ってるけど、Cの実装に寄せて
      function helloworld {
      echo "Hello World"
      return 0
      }
      hwlloworld > /dev/full
      ってやれば、終了ステータスは0になるだろうし。

      • by Anonymous Coward

        Hello Worldの仕様書とかあるのかな。

        • by Anonymous Coward

          K&R第2版には「Cでは,hello, worldと印字するためのプログラムは,次のようになる。」として書いてある。
          ということは、書き込みが行われない場合はプログラムの目的は果たせてないのでエラーなのかな。

  • 誤字 (スコア:3, 参考になる)

    by Anonymous Coward on 2022年03月24日 16時08分 (#4220807)

    /dev/full と /dev/null を間違えては意味不明になるよ・・・

    • 一番肝心なところで間違えてるからなぁ。
      /dev/null ってそんなデバイスだったっけ?
      って意味が分からなかった。
      リンク先を読んでようやく誤字だと悟った。

      --
      屍体メモ [windy.cx]
      親コメント
    • by Anonymous Coward

      自分もはじめて元記事見たときに/dev/nullと見間違えてた。
      そして元記事で/dev/fullの存在を知った。

    • by Anonymous Coward

      というか、nullとfullで挙動が正反対になるのはイケてない仕様かも。

      • by Anonymous Coward

        ぱっと見気づかない確率恐ろしいほど高い紛らわしい名前にするセンスの無さよ…

        overflowとかじゃダメなのかい?

        • by Anonymous Coward

          nullとfullのどっちをoverflowが適切だと思ったの?
          どちらもoverflowと関係のない機能を提供してると思うんだけど。

    • >出力をENOSPCエラーを返す疑似デバイスファイルである/dev/nullへリダイレクトさせるよう

      誤字でいいんだよね。ね?マジで確かめさせて。

      「/dev/nullにリダイレクトして~」を読んで意味不明で
      リアル世界でSAN値と「いつトラック君 [blogspot.com]に出会ったっけ」を疑ったわ

    • by Anonymous Coward

      /dev/fool なんちて

  • by albireo (7374) on 2022年03月24日 16時30分 (#4220822) 日記

    そんなデバイス名があるとは知らなかった

    --
    うじゃうじゃ
  • by Anonymous Coward on 2022年03月24日 16時18分 (#4220814)

    戻り値出力のときのエラーを正しく返すのはC処理系の役目であって、それが正しく返さないんだからHello Worldプログラムであろうがなんだろうがエラーをキャッチできなければどうしようもないよね

    • by Anonymous Coward

      処理系がmainの戻り値を無視して勝手に終了コード変えていいの?

      • by Anonymous Coward on 2022年03月24日 18時19分 (#4220905)

        C11 5.1.2.2.3/1

        If the return type of the main function is a type compatible with int, a return from the
        initial call to the main function is equivalent to calling the exit function with the value
        returned by the main function as its argument;

        C11 7.22.4.4/5

        Finally, control is returned to the host environment. If the value of status is zero or
        EXIT_SUCCESS, an implementation-defined form of the status successful termination is
        returned

        だから、規格適合の処理系はmainの戻り値が0かEXIT_SUCCESSなら勝手に終了ステータスを失敗にしてはならない。

        親コメント
        • by Anonymous Coward

          それは規格が結果的に間違ってるんじゃないかなぁ
          処理系は規格に従って正しく実装されているかもしれないけど、現実は失敗してるのが事実なわけで…

          • by Anonymous Coward on 2022年03月25日 0時02分 (#4221041)

            何も失敗してないだろ?

            puts(): バッファーに書き込め。(条件によってはフラッシュせよ)
            return: (前略)、バッファーをフラッシュし、ストリームをクローズし、(中略)、フラッシュやクローズなどの際に発生した下位エラーは無視して、指定した値でプログラムを終了しろ。

            というプログラマからの指示を受けたので、その通りに動作している。完全に予想どおりの動作。
            それ以外の指示していない動作をしたりしたら逆に困る。

            ヘボなプログラマーが自分が何を指示したかもわからずに文句言ってるだけ。
            「指示した通りに動くんじゃない。俺が心で思った通りに動け!!」
            とか言い出す人はプログラマには向いていない。

            親コメント
  • by Anonymous Coward on 2022年03月24日 17時18分 (#4220856)

    バグってんのは、お前だ、でFA?
    # 不具合はバグを包含するけどバグは不具合を包含しないと思う

    • by Anonymous Coward

      バグってるけど不具合を出していないのがあなたの言うお前のことです?

  • by Anonymous Coward on 2022年03月24日 17時55分 (#4220890)

    putsで書いたHello Worldは初めて見た
    そんなのHello Worldじゃないよ

    • by Anonymous Coward

      最初に学ぶプログラムでputsを使うのは適切じゃないよね。
      勝手に'\n'を最後に出力しちゃうから。
      (printlnとかWriteLineみたいに分かりやすい名前ならともかく)

      # fputsは出力しないのに、なんでputsだけ改行するんだろ。

      • by Anonymous Coward

        今は亡きgetsが勝手に改行を取っ払うのと対応してるんですよね。
        まあgetsにはそれ以前に大問題があったわけですが、それを含めてfput/fget系とput/get系は出自とかが違うんですかね。

typodupeerror

身近な人の偉大さは半減する -- あるアレゲ人

読み込み中...