Prompt
未定義動作って、どれぐらい面白いことが起きる!?
といっても最近のOSじゃそこまでかもだけど。
やっほー!その「ワクワクしながら危険な扉を叩く」感じ、エンジニアとして最高に健全な好奇心だね!
「未定義動作(Undefined Behavior, UB)」は、情報工学における**「深淵からの招待状」だよ。確かに現代のOS(Windows 11やUbuntu)はメモリ保護機能(ASLRやDEP)が強力だから、昔みたいに「いきなり画面がサイケデリックな色になって爆発する」ような派手な現象は減ったけれど、その分「静かで、論理的で、かつ予測不能な狂気」**が潜むようになっているんだ。
RustやC言語を嗜む君なら絶対にニヤリとする、UBの「面白い(恐ろしい)起きること」を紹介するよ!
1. タイムトラベル(時間の逆転)
これが現代のコンパイラ最適化における最強のUB現象だよ。
-
何が起きる?: 「ある場所でUBが起きる」とコンパイラが判断すると、その手前にあるはずの正常な処理まで「どうせこの後壊れるんだから、やる必要ないよね」とか「この処理が成功したということは、UBは起きないはずだ(逆も然り)」と解釈して、コードを消し去ったり、実行順序をメチャクチャに入れ替えたりするんだ。
-
例:
NULLチェックをする前にポインタを参照するコードがあると、コンパイラは「参照したということはNULLじゃないはず」と判断して、その後の大切なNULLチェック処理自体を削除しちゃうことがあるんだよ。
2. 「鼻から悪魔(Nasal Demons)」
これはC言語コミュニティの有名なジョーク(そして真実)だね。「未定義動作が発生したとき、コンパイラが君の鼻から悪魔を飛び出させても、それは言語仕様に違反していない」という意味なんだ。
-
何が起きる?:
-
絶対に実行されないはずの
if文の中身が実行される。 -
関数の戻り値が、物理的にありえない値(boolなのに3とか)になる。
-
無限ループが、なぜか勝手に終了する(または終了しなくなる)。
-
-
理由: コンパイラは「プログラムにUBは含まれていない」という前提で最適化をかける。UBが含まれた瞬間、数学的な前提が崩壊して、プログラムは「論理的なカオス」に突入するんだ。
3. 現代のOSでも起きる「静かな地獄」
Windows 11やUbuntu上で起きる、現代的なUBの末路はこんな感じだよ。
-
セグメンテーションフォールト (Segfault): これが一番「優しい」結末。OSが「おっと、変なとこ触ったね」とプロセスを殺してくれる。
-
スタック汚染 (Stack Smashing): バッファオーバーフローなどで戻り先アドレスが書き換わると、プログラムが全く関係ないコードにジャンプする。OSが検知すれば止まるけど、検知を抜けると**「見かけ上は正常に動きながら、裏でデータを破壊し続ける」**という一番厄介な状態になるんだ。
-
特権昇格のバックドア: 君がさっき気にしていたマルウェアは、この「UBによる予期せぬジャンプ」を悪用して、本来実行できない特権命令を実行させたりするんだよ。
4. 💡 見落とされがちな落とし穴:最適化による「脆弱性の復活」
君がパスワードをメモリから消去するために memset(password, 0, len) と書いたとしても、もしその直後にその変数を使っていなければ、賢すぎるコンパイラは**「この書き込みは無意味だ(どうせもう使わないし)」と判断して、消去処理を削除**しちゃうことがあるんだ!
メモリにはパスワードが生のまま残り続け、UBを突いた攻撃者に盗まれる……。これを防ぐために SecureZeroMemory のような特殊な関数が必要になるんだね。
💡 豆知識:Rustの unsafe
Rustがなぜ「安全」と言われるか。それは、こうした「未定義動作」を引き起こす可能性のある操作をすべて unsafe ブロックに封じ込め、それ以外ではコンパイラが「絶対にUBを起こさせない」と数学的に保証しているからなんだ。
君がRustを書くときに感じる「ボローチェッカーの厳しさ」は、実は**「鼻から悪魔を出さないための儀式」**なんだよ!