C言語の未定義動作、C2yで45項目が削除へ
Reducing undefined behavior in the C language
生体医工学の教授Martin Uecker氏がKernel RecipesでC言語の未定義動作とメモリ安全性について講演。C標準には約100の未定義動作があるが、C2yドラフトで45が削除された。コンパイラの警告やサニタイザー、形式検証などのツールも進化しており、Cは依然として改善を続ける生きた言語だと結論づけた。
標準は未定義動作に対してコンパイラがあらゆることをする権利を与えており、鼻の悪魔を呼び出すことさえ可能だ。
HNでの議論
149- pizlonator
Fil-Cに言及しているのはいいけど、それでも過小評価している。TFAもCHERIを過小評価している。Fil-Cは単に「多くのtemporal-safetyバグを見つける」だけじゃない。エクスプロイトを書く者にとって、メモリ安全性のバグ(spatialとtemporal)を封じ込め、言語全体に厳密なセマンティクスを与える。CHERIはいくつか異なるトレードオフをするが、メモリ安全性のエクスプロイトが機能しない程度に十分厳密なセマンティクスも与える。CHERIとFil-CはどちらもRustより包括的だ。なぜならABIレベルで問題に取り組むからだ(そのため、新しい言語の安全なサブセットで書き直された部分にしか保護が適用されないという問題が起きない)。Rustはコンパイル時間の点で優れていると言えるかもしれないが、セマンティクスの定義性やエクスプロイト可能性を気にするなら、それは大きな違いにはならない。
- chasil
「例えば、Honeywellのマシンの中には9ビットバイトのものもあった」
OS 2200は36ビットワードだ。今でもサポートされているプラットフォームだ。
https://en.wikipedia.org/wiki/UNIVAC_1100/2200_series
このプラットフォームは最初のSMP UNIX実装だった:
「Sperryが提供するあらゆる構成、マルチプロセッサ構成を含め、UNIXシステムを実行できる」
https://www.nokia.com/bell-labs/about/dennis-m-ritchie/other...
- vrighter
実は、UBを本当にUBにするジョークプロジェクトを始めようと考えていた。別に「実際には符号付きオーバーフローは現代のほとんどのCPUで同じように処理される」という話じゃない。RNGとコンパイラプラグインシステムを使って、Uに合わせて新しいBを導入するという意味だ。
- hn_submit
これは見当違いだと思う。私見ではCはシステムプログラミング用の「高水準アセンブリ」にすぎない。未定義動作(UB)と戦うために実行時動作を追加すれば、実行時間が爆発する。静的解析もコンパイル時間を爆発させずにできることには限界がある。
Cは「適材適所」のツールであり、それはオペレーティングシステムと、毎秒何千回も呼ばれるそのコードだ。そんなコードに実行時チェックをほんの少しでも入れる余裕はない。開発者は自分が何をしているか知っているべきだし、知らないなら厨房から出て行くべきだ。
アプリケーションプログラミングでのCの使用は控えるべきで、アプリ開発者にはRustやGoのようなメモリ安全な言語を促すべきだ。
それに、UBに関する限りRustがこのケースを解決するかどうかもよくわからない。
- rwmj
https://en.wikipedia.org/wiki/CompCert は少なくとも言及されるべきだと思う。自由ソフトウェアではない(「ソース公開」ライセンス)としても。安全クリティカルな業界で大規模なCコードベースを持つ企業がコードを検証する実際の方法がこれだ。
- vbezhenar
CのUBに関する最大の問題は、それが静かであることだ。
コンパイラが勝手なことをするのは構わない、まあ、なんでも。いや、構わないわけではないが、この狂った世界では受け入れられる。
でも大きな警告が欲しい! 例えば「警告:以前のゼロ除算がUBであるため、この条件演算子は一方の分岐に折りたたまれました」のように。そうすれば気づいて書き直したり、その条件を削除したりできる。
このコードがマクロ展開の結果である可能性があることは理解している。それは構わない。マクロは特定の診断を一時的に無効にするプラグマを含めるか、マクロを編集できない場合はユーザーがマクロの使用をこれらのプラグマで囲むべきだ。他の警告でもすでに起きていることだ。
あるいはコンパイラがマクロ展開と正直なユーザーのミスを区別できるほど賢いかもしれないが、わからない。
C++コンパイラが、私が単純な無限ループを書いた関数のエピローグをただ削除したことを覚えている。あまりに狂っていた。無限ループに入る代わりに、プログラムはたまたま下にリンクされていた関数を実行し続けた。それをデバッグすることを想像してほしい。診断はゼロだ。
- 1vuio0pswjnm7
1790620504 | Reducing undefined behavior in the C language | https://lwn.net/SubscriberLink/1095811/b9325731ea9b61e0/ | https://news.ycombinator.com/item?id=49882419 | 15 comments
1790673092 | Reducing undefined behavior in the C language | https://lwn.net/SubscriberLink/1095811/efcdbcf080cfa4c6/ | https://news.ycombinator.com/item?id=49890290 | 0 comments
- nine_k
UBをめぐる状況は正直不可解だ。私の理解では、この考えは非常に貧弱なコンパイラの時代に生まれた。当時のコンパイラは、明確な意味をなさないものを翻訳したり、特定のアーキテクチャで予測不能に動作したりしていた。しかし、なぜ50年経った今でもこれが続いているのか?
コンパイラがUBを検出できるなら、唯一合理的な方法はプログラムを即座に終了させることだと思う。「鼻から悪魔」を放出するのではなく。(そしてもちろん、多くのバグは-Wallで粉砕される。これはデフォルトにすべきだ。)
もちろん、記事で言及されている構造体のパディングバイトの読み取りのように、すべてのUBがコンパイル時に検出できるわけではない。すべてのUBを任意だが予測可能な方法で(即時クラッシュで構わない)オプトアウトする方法があればいいのに。実行時に起こりうるあらゆるUBに対する-fsanitizeのようなものだ。かなり重要なコードでは、パフォーマンス低下を許容してもいい。後始末やRCEエクスプロイトに対処するより安くつくかもしれない。とはいえ、完全に現実的ではないこともわかっている。