今日ふと思いついたこと。x86なLinuxでgccを使ってプログラムを作成すると、普通eaxに関数の返り値が入る。でも、long longは8バイトつまり64bitなので、32bitのeaxには入りきらない。ちょっと調べればわかることなんだけど、これをどうやって返しているのかを調べたのでメモしておく。環境はGentoo Linux 13.0 (amd64)、gccはportageから入れた4.6.3p1.11。
まず、適当にlong longな値を返す関数を書いてみます。
/* return_longlong.c */ long long return_64bit() { return 0x12345678deadbeef; } int main() { return_64bit(); }
これを適当にgcc -g -m32 return_longlong.c
とかでコンパイルしてobjdump -S a.out
してみます。してみた結果が以下。
080483d4 <return_64bit>:
long long return_64bit()
{
80483d4: 55 push %ebp
80483d5: 89 e5 mov %esp,%ebp
return 0x12345678deadbeef;
80483d7: b8 ef be ad de mov $0xdeadbeef,%eax
80483dc: ba 78 56 34 12 mov $0x12345678,%edx
}
80483e1: 5d pop %ebp
80483e2: c3 ret
つまり、上位32ビットはedx、下位32ビットはeaxに入るようです。cdecl呼び出し規約では関数内でeax, ecx, edxが自由に使用できるから、返り値の上位32ビットを格納する場所にedxが選ばれたんだと思います。ecxが使用されない理由はちょっと思いつかないです。気まぐれかな?
確証はないけれど、下位32ビットがedxではなくeaxに入るのは、もしreturn_64bit()が32bitの値を返すだろうと期待して呼び出されても大丈夫なようにだと思います。例えば、char型変数に32bitの値0x12345678を代入しようとした場合に下位8bitの0x78が代入される、つまり、入りきらない上位の値は切り捨てられるのと同じです。仮にedxに下位、eaxに上位を入れてしまうと、eaxに32bitの返り値が入ってると思って処理を行った場合、上の例で言えば下位の0xdeadbeefが取得したかったのに、上位の0x12345678が取得されて不都合だからだと思います。
ちなみに、64bitでコンパイルするとこんな感じでeaxの64bit版的なレジスタraxに返り値が入ります。また、試しに0x12345678deadbeefを0xdeadbeefに変えてコンパイル→逆アセンブルしなおしたところ、返り値が32bitで表せればraxは使わずeaxで返り値を返すという動作をするようです。
00000000004004e4 <return_64bit>:
long long return_64bit()
{
4004e4: 55 push %rbp
4004e5: 48 89 e5 mov %rsp,%rbp
return 0x12345678deadbeef;
4004e8: 48 b8 ef be ad de 78 movabs $0x12345678deadbeef,%rax
4004ef: 56 34 12
}
4004f2: 5d pop %rbp
4004f3: c3 retq
00000000004004e4 <return_64bit>:
long long return_64bit()
{
4004e4: 55 push %rbp
4004e5: 48 89 e5 mov %rsp,%rbp
return 0xdeadbeef;
4004e8: b8 ef be ad de mov $0xdeadbeef,%eax
}
4004ed: 5d pop %rbp
4004ee: c3 retq