Diary
twitterもはじめました
研究で独自アーキテクチャ用のアセンブリ言語・アセンブラを作ってそのテスト中に気づいた…。
まず,普通にCでfloatの値を表示するプログラムを書いてみる。 とりあえず,このファイルをfloat.cとしておく。
int main(int argc, char *argv[])
{
float val = 3.14159;
printf("Value: %f\n", val);
return 0;
}
そしてこれをコンパイルして実行すると
と出る。これは期待した動作をしている。
さて,ここでgcc -S float.c
としてアセンブリで出力してみると
.section .rodata
.LC1:
.string "Value: %f\n"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
movl $0x40490fd0, %eax #注: この16進数即値はfloatの3.14159
movl %eax, -8(%ebp)
flds -8(%ebp)
fstpl 4(%esp)
movl $.LC1, (%esp)
call printf
movl $0, %eax
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.3.2-1ubuntu11) 4.3.2"
.section .note.GNU-stack,"",@progbits
注目して欲しいのは太字の部分。 下に抜き出したものと,それぞれの命令の動作について書いてみる。
movl %eax, -8(%ebp) # 3.14159 を%ebp-8の指すメモリに移動
flds -8(%ebp) # 浮動小数点レジスタに%ebp-8の指すメモリから移動
fstpl 4(%esp) # 浮動小数点レジスタの内容を %esp+4の指すメモリに移動(スタックに積む)
なぜいちいち浮動小数点レジスタを介すのか? 欲しい値はすでに一つ目の命令で%eaxに入っているはずで, それをそのままスタックに積めば目的の動作を実現することができるはず。 なので,浮動小数点レジスタを介さずにプログラムを書き直してみると
movl %eax, 4(%esp) # 3.14159 をスタックに積む
このように書ける。
ここでこのプログラムをgcc float.s
でコンパイルして実行してみると
こんなワケのわからん値が出てくる。しかもこの値,実行するたびに変わりる。 ちなみに,
movl %eax, -8(%esp)
のようなコードを挟んでやると,プログラムの出力は常にValue: 0.00000
になる。
なぜこのような現象が起こるのか?
実は"Value %f"は32bitの浮動小数点(float)を表示するのではなく, 64bitの浮動小数点(double)を表示するものとして実装されているっぽい(少なくともIA-32では)。 なので一度浮動小数点レジスタを介してfloatからdoubleに変換していたというワケ。 じゃあfloatをそのまま表示することはできないのか?はたぶんYes。
なぜこんな実装になっているのかはよくわからないんだけども, IA-32で浮動小数点を扱う場合,(MMXやSSEを使わなければ)必ずx87由来の80bitの精度を持った浮動小数点レジスタを使うことになる。 float同士の演算であっても,メモリへの結果の格納を除いてすべて80bitの精度で計算されるし,
とやったとき,GCCはデフォルトで2.71はdouble型として定義する。 ここらへんの開発者はIA-32ではfloatを使うよりdoubleを使う方が自然だと判断している? もしかすると,x87は4バイトのデータを扱うのが苦手なのか? 決定的な理由はよくわからないけど,そうなっているのである…うーん
ということで,よくC言語入門テキストに書いてある
なんて文言は嘘なので信じないように! 俺はこの文言を信じて自分の作っている仮想CPUにバグがあるんじゃないかと数時間疑い,その時間を無駄にしたのだから! (少なくともIA-32, libc.so.6では)
# こっそり追記
# floatに %f, doubleに %lf を使うというのは,
# 他のアーキテクチャへの移植という観点から正しいかもしれない
# ただ,今回のような例外もあることを書いておかないと
# アセンブリから直接libcを叩くときに変な罠にハマってしまう
# そういうところは注意が必要