cast の怪
T 研のデフォルトが LiL だと思ったら大間違いだ。演習3の時からずっと思っていたが、むしろ C++。構造化データ+大規模反復計算となると、ある意味必然かもしれない。「ふつうに、./configure; make; make install すればつかえます、10 GB くらいメモリがあれば」。いったいどこがふつうなのか。
今日ちょっと出た話題。gcc でポインタをキャストしてから代入すると、コンパイラで挙動が変わって困ったなという話。ポインタをキャストして代入してもとの型に戻すって、それこそなにやってるんだという話ですが、いやだからといって挙動が変わるって、いやしかしそんなアホな。
float n; *((int*)&n) = 3; cout << n << endl; cout << *((int*)&n) << endl;
実際に問題のソースを見せてもらったわけではないんだけど、多分こういうことなんだろうと推測。ホントかなぁ・・・。こんなキャストが必要になってくるのは、某インタプリタ上での話なのだが。いわゆる C++ でコンパイラ作るのムズカシイよね、という話と同じ*1。だから、あれほど OCaml で(略。
実際に上のソースを g++ 4.0.3 でコンパイルしてみたら、確かに挙動が変わるのだ。
$ g++ -O1 -Wall cast.cc $ ./a.out 4.2039e-45 3 $ g++ -O2 -Wall cast.cc $ ./a.out 2.02129 3
というか、こういう操作はやめましょうよ、ねぇ。gcc のバグなのか、仕様が不定なのか。まぁ、ボクは使いませんからどっちでもいいですが・・・。
アセンブリではかせると状況が分かりました。代入前後のソースを比較すると、こんな感じ。まずは、-O1
andl $-16, %esp subl $16, %esp movl $3, -4(%ebp) flds -4(%ebp) fstpl 4(%esp) movl $_ZSt4cout, (%esp) call _ZNSolsEd
movl $3, -4(&ebp) が代入部分。そのあとに、flds -4(%ebp) が多分スタック操作だと思うんだけど・・・。マニュアルに見あたらないのはドウシタモノカ。
これを -O2 にすると、これが困ったことになる。
andl $-16, %esp flds -4(%ebp) subl $16, %esp fstpl 4(%esp) movl $3, -4(%ebp) movl $_ZSt4cout, (%esp) call _ZNSolsEd
あれ? flds と movl の前後関係が逆になってしまった。逆って、あんたそりゃ挙動変わりますよ。-O2 で吐いた、上の .s 中の flds と movl を入れ替えてコンパイルし直すと、やっぱり -O1 のときと同じ挙動になったから、この辺が問題っぽい。うーん、バグかなぁ。float として扱われるべきところに、int を代入しちゃっているわけで、型ごとに flow 解析して最適化しているなら、こういう不具合もあり得そうですねぇ。といっても、あとは想像の域を脱しませんが。てか、ちゃんと union 使ったら動きます。