C言語の未定義動作でSIGTRAP

全てのCプログラマが未定義な振る舞いについて知っておくべきこと #1/3
http://blog-ja.intransient.info/2011/05/c-13.html


LLVMコンパイルしたコードは、最適化を有効にしていると
たまにSIGTRAPシグナルを生成するのはなぜなのか、と聞かれ
ることがある。いろいろ調べたあと、(X86での話だが) Clang
は "ud2" インストラクションを生成していたことがわかった
。"ud2" は__builtin_trap()が生成するインストラクションと
同じものだ。[訳注: #UD例外を発生させる命令。ソフトウェア
が#UD例外をハンドルできているかテストするために使われる。
つまり、ソースコードが未定義な振る舞いを使っていたから、
LLVMはud2インストラクションを生成したのであって、LLVM
バグではない、ということ]

未定義命動作の場合にud2命令を埋め込むのか。
SIGTRAPはデフォルトがアボートなので、いきなりアボートしたとき、プログラムにバグ(未定義)がある可能性があるのか。うーん




符号付き整数のオーバーフロー: 例えばint型に対する演算が
オーバーフローすると、結果は未定義となる。

2つの整数の和のオーバーフローの判定を、小さくなるかどうかでやってた(ことがあった)けど、未定義だった:P。


float *P;
void zero_array() {
int i;
for (i = 0; i < 10000; ++i)
P[i] = 0.0f;
}

を、"memset(P, 0, 40000)"に最適化できる。また、多くの
メモリロードをループの前に移動させる、共通部分式を削除
する、という最適化も可能になる。この種の未定義な振る舞い
は、-fno-strict-aliasing フラグを指定することで無効に
することができ、この解析も無効になる。

P[0]=0.0f, P[1]=0.0f, ... を実行する。
これは一見連続のメモリを初期化するように見えるので
memsetで最適化できそうに見える。が、aliasの場合、
つまりP[0]=0.0fによってPの値が変わる場合
連続のメモリの書き換えでは異なる動作をすることになる。



http://blog-ja.intransient.info/2011/05/c-23.html


もっと具体的に説明するために、馬鹿馬鹿しい例を見てみよう
(Linux Kernelで見つかったセキュリティーホールになり得るバグを簡略化したもの)

void contains_null_check(int *P) {
int dead = *P;
if (P == 0)
return;
*P = 4;
}


しかし、もしオプティマイザの構成が異なっていて、"デッド
コード削除"よりも先に"冗長なNULLチェック削除"を実行した
とすると、このようになるだろう。
...
多くの (分別のある!) プログラマは、この関数からNULLチェック
が削除されるととても驚くようだ (そしてほとんどの場合、
コンパイラのバグとして報告される)。しかし、C標準によると
、"contains_null_check_after_DCE_and_RNCE" と "contains_
null_check_after_RNCE_and_DCE" との両方とも完全に有効な
最適化であり、様々なアプリケーションのパフォーマンスを
向上させるために、二つの最適化があることが重要なのだ。

うむむ。


最適化済みのコードをデバッグすることは何の役にも立たない

ある種の人々 (例えば、組込み系の低レイヤプログラマで、
生成されたマシンコードを見るのが好きな人々) は、最適化を
有効にしたままで全ての開発を行う。開発中のコードがバグを
含むことはよくあるので、こういう人たちは、デバッグするの
が難しい実行時の振る舞いにつながる、驚くような最適化を
無駄に多く見ることになる。

役に立たないの意味がよくわからないが、アセンブラ層でデバグをする場合、最適化済みのコードは元のコードのバグが複雑に組み込まれてるので必要以上に苦労する、ぐらいの意味か。


NULLポインタを呼び出すのは未定義なため、call()の前にset()
が呼ばれたと仮定することが許されるので、こういう最適化を
して良い。この場合、開発者は"set"を呼ぶのを忘れたが、
NULLポインタデリファレンスでクラッシュすることはなく
[訳注: 最適化オプションをつけて開発していたため]、他の人が
デバッグビルドを使ったときにコードが壊れた。

最適化をつけると、期待通りに動いて、最適化しないと問題が出る事例。



未定義な振る舞いを使用している"動いている"コードは、
コンパイラが進化したり変わったりすると"壊れる"

結論