そもそも Undefined Behaviorとは
前に @akinomyoga 氏がcpprefjpにまとめられていたので引っ張っておきましょう。
https://cpprefjp.github.io/implementation-compliance.html#behavior
未定義の動作 (undefined behavior; 通称 UB) は、処理系が実際に行う動作について標準規格が如何なる要件もおかないことを表す。
https://cpprefjp.github.io/implementation-compliance.html#nasal-demon
プログラムが
- 未定義の動作 (UB) を引き起こすとき、
- または診断不要 (NDR) の規則に違反している
とき、標準規格は適合する処理系に対して何らの要件も課さない。 つまり、UB または NDR 違反を含むプログラムに対して処理系がいかなる動作をしても規格には抵触しないということを表す。 例えば、このことで処理系が鼻から悪魔を出しても、それはプログラムの作者の責任であり、その処理系を責めることはできない。 この冗談を鼻から悪魔 (nasal demons)12 と呼ぶ。鼻から悪魔を出す処理系は今のところ実在しないが、 実際の未定義の動作として最適化の過程で或る種の「タイムトラベル」を起こす処理系は実在する3。
なぜUndefined Behaviorなんてあるの?
他の言語では例外を投げることになっていたりする内容がC/C++ではUndefined Behaviorになっていることがある。
これはC/C++が速度を追求する言語であり、抽象化によってプログラマが自分で挙動を制御できなくなることを避ける傾向にあること、例外などは実行時にコストがかかること、Cには例外がなかったことなどが考えられる。
プログラマは全知全能ではないので
そんなC/C++を操るプログラマがもし、全知全能であれば、Undefined Behaviorをうっかり踏んでしまうことはなかっただろう。しかし現実にはプログラマもまた人間であり、過ちを犯す生き物である。
なにかやりたいことがあるときに、Google検索で調べながら書く、というのは日常的に行われる行為であろうが、その検索で引っかかるコードもまた人が書いたものであり、Undefined Behaviorを踏んでいる可能性がある。
闇のC++ Undefined Behaviorに対する防衛術
@_EnumHack 氏が以前
闇のC++ Undefined Behaviorに対する防衛術
という記事を書いている。防衛術としては
- コンパイラの警告レベルを上げる(
-Wall -Wextra
とか) - 複数のコンパイラでコンパイルする
- Undefined Behavior Sanitizerを使う
- 規格書を読む
- 使っている機能・標準ライブラリについて調べる
- コードレビュー、複数人開発
- テスト
などが紹介されています。付け加えるなら
- 強い人にTwitterで聞く
なんてのもあるかもしれません。
結局規格書を読むハメになる
もちろんこれらの防衛術は非常に有効で、Undefined Behavior Sanitizerは結構頑張ってくれます。
しかし、結局規格書を読むハメになる、という事態が生じます。
そんなとき、規格書はこのコードはUndefined Behaviorかどうかを解説することを目的としていないので、極めて難易度が高く、挫折し、Undefined Behaviorを踏んでしまうでしょう。
Undefined Behaviorを放置できない時代がすでに来ている
Undefined Behaviorを踏んでしまっても、処理系がプログラマーに忖度して、あるいは技術力不足で最適化に利用されない時代であれば、実害はなかったかもしれません(ほんとか?
しかし現代のコンパイラは積極的に最適化を行うので、明らかに実害が出ます。
- コンパイラのリミッタが外れつつある今、null安全は必須なのかもしれない
- 鼻から悪魔:不定値(indeterminate value)バージョン
- C++でうっかり無限ループを書くと鼻から悪魔が出てくる
C++標準化委員会は踏み抜きやすいUndefined Behavior TOP100のようなものを公式に出すべきではないか
こうなるとそもそもC++標準化委員会が公式で解説を書くべきではないか?と思えてきます。
踏み抜きやすいUndefined Behavior TOP100のようなものをリストアップして、現役のC++プログラマやもしくはC++プログラマになろうとしている人に向けて解説と回避方法を書いたものを作成するべきではないでしょうか?
Undefined Behaviorとはもはや処理系を作る人や一部のマニアだけではなくてすべてのC++プログラマが知らなければならない知識なのですから。
C++標準化委員会にはC++の教育について扱うStudy Group、SG20 Educationというのがあります。
まさにC++プログラマへの教育・啓蒙の範疇の話ですからここが適当なんじゃないでしょうか?(知らんけど
なんでchar8_tが必要か理解するためにあえて先に教えないとかのたまっている暇があったらそういう活動をしてほしいものです。
この記事を執筆した経緯
ところで未定義の挙動になるパターンについては重箱の隅と言われようとも積極的に覚えていこうな。コンパイラが賢くなればなるほど、今まで顕在化しなかったUBパターンが表面化しやすくなるので。
— yoh2 (@yoh2_sdj) 2019年4月10日
というかコンパイラがアグレッシブにUBを最適化に活用する現状、C++標準化委員会は公式に特に警戒するべきUBのリストみたいなのを発行するべきなんじゃないか?
— yumetodo-鳥の氷河から逃げる (@yumetodo) 2019年4月10日
だれもあのクソ長大な規格書をいちいち引いてUBかどうか判定する(しかも大抵間違えて解釈する)なんて作業したくねーだろ https://t.co/cGVyt1t5Ay
ほんとほしいよねこれ。
— yoh2 (@yoh2_sdj) 2019年4月10日
分かりづらいところに書かれていてもUBだと名言されているならまだましで、規格書内のどこにもこのパターンに関する言及はないからUBとなるものに関しては、範囲が有限とはいえある意味悪魔の証明を求められるわけで……https://t.co/hW8nGM14dF
C++ の規格書読むよりツイッターでつぶやいたほうが早い
— バンビちゃん@実際存在しない (@pink_bangbi) 2019年4月10日
それがおかしい。
— yumetodo-鳥の氷河から逃げる (@yumetodo) 2019年4月10日
というかSG20 Education はchar8_t型の重要性を説くためにあえて教えないとかそういう馬鹿げたことに労力を費やすんじゃなくて、踏み抜きやすいUBリストTOP100とかそういうのを作るべきだろ、仕事しろ。 https://t.co/atElMGyt6u