スタックで遊ぶ
これはEsolang Advent Calendar 2011(http://atnd.org/events/22463)の6日目の記事です。
難解言語でもなんでもないのですが、少しC言語でスタックをいじって遊んでみました。
いじるものといっても変数とリターンアドレスしか無いので、今回はリターンアドレスを戻してループ(再帰)を再現してみました。
この記事のソースコードはUbuntu11.04,gcc4.5.2でのみ動作確認しました。もしかしたらこの記事のソースをコンパイルしても動かないこともあるかもしれません。ご了承ください。
階乗
再帰といえば階乗です。まずは階乗を実装してみました。
#include <stdio.h> void factorial(int n) { int a[1]; if(n < 1) { return; } a[0x2] -= 0xc; --a[0x8]; a[0x9] *= n; } int main(void) { int n; int result = 1; int t; printf("input a number:"); scanf("%d", &n); t = n; factorial(t); printf("%d! = %d\n", n, result); return 0; }
実行結果は以下のとおりです。
input a number:8 8! = 40320
ちなみに、実行ファイルをgdbでdisassembleしたものと最初にfactorialが呼ばれた直後のスタックの内容が以下です。
main
(gdb) disas main Dump of assembler code for function main: 0x08048441 <+0>: push %ebp 0x08048442 <+1>: mov %esp,%ebp 0x08048444 <+3>: and $0xfffffff0,%esp 0x08048447 <+6>: sub $0x20,%esp 0x0804844a <+9>: movl $0x1,0x1c(%esp) 0x08048452 <+17>: mov $0x8048570,%eax 0x08048457 <+22>: mov %eax,(%esp) 0x0804845a <+25>: call 0x8048338 <printf@plt> 0x0804845f <+30>: mov $0x8048580,%eax 0x08048464 <+35>: lea 0x14(%esp),%edx 0x08048468 <+39>: mov %edx,0x4(%esp) 0x0804846c <+43>: mov %eax,(%esp) 0x0804846f <+46>: call 0x8048348 <__isoc99_scanf@plt> 0x08048474 <+51>: mov 0x14(%esp),%eax 0x08048478 <+55>: mov %eax,0x18(%esp) 0x0804847c <+59>: mov 0x18(%esp),%eax 0x08048480 <+63>: mov %eax,(%esp) 0x08048483 <+66>: call 0x8048414 <factorial> 0x08048488 <+71>: mov 0x14(%esp),%edx 0x0804848c <+75>: mov $0x8048583,%eax 0x08048491 <+80>: mov 0x1c(%esp),%ecx 0x08048495 <+84>: mov %ecx,0x8(%esp) 0x08048499 <+88>: mov %edx,0x4(%esp) 0x0804849d <+92>: mov %eax,(%esp) 0x080484a0 <+95>: call 0x8048338 <printf@plt> 0x080484a5 <+100>: mov $0x0,%eax 0x080484aa <+105>: leave 0x080484ab <+106>: ret End of assembler dump.
factorial
(gdb) disas factorial Dump of assembler code for function factorial: 0x08048414 <+0>: push %ebp 0x08048415 <+1>: mov %esp,%ebp 0x08048417 <+3>: sub $0x10,%esp 0x0804841a <+6>: cmpl $0x0,0x8(%ebp) 0x0804841e <+10>: jle 0x804843e <factorial+42> 0x08048420 <+12>: mov 0x4(%ebp),%eax 0x08048423 <+15>: sub $0xc,%eax 0x08048426 <+18>: mov %eax,0x4(%ebp) 0x08048429 <+21>: mov 0x20(%ebp),%eax 0x0804842c <+24>: sub $0x1,%eax 0x0804842f <+27>: mov %eax,0x20(%ebp) 0x08048432 <+30>: mov 0x24(%ebp),%eax 0x08048435 <+33>: imul 0x8(%ebp),%eax 0x08048439 <+37>: mov %eax,0x24(%ebp) 0x0804843c <+40>: jmp 0x804843f <factorial+43> 0x0804843e <+42>: nop 0x0804843f <+43>: leave 0x08048440 <+44>: ret End of assembler dump.
最初にfactorialが呼ばれた直後のスタック(入力は8)
(gdb) x/32x $ebp 0xbffff688: 0xbffff6b8 0x08048488 0x00000008 0xbffff6a4 0xbffff698: 0x0028bff4 0xbffff6b8 0x0015ed35 0x00000008 0xbffff6a8: 0x00000008 0x00000001 0x080484b0 0x00000000 0xbffff6b8: 0xbffff738 0x00145e37 0x00000001 0xbffff764 0xbffff6c8: 0xbffff76c 0x0012e414 0xffffffff 0x0012cff4 0xbffff6d8: 0x0804824b 0x00000001 0xbffff720 0x0011da31 0xbffff6e8: 0x0012dad0 0xb7fffb48 0x00000001 0x0028bff4 0xbffff6f8: 0x00000000 0x00000000 0xbffff738 0x7f063c5a
最大公約数
最大公約数(Greatest Common Divisor)を求めるプログラムも作ってみました。
#include <stdio.h> void gcd(int m, int n) { int a[1]; if(m < n) { m ^= n; n ^= m; m ^= n; } if(n <= 0) { a[0xc] = m; return; } a[0x2] -= 0x14; a[0xd] = m % n; a[0x0e] = n; } int main(void) { int a, b; int result; int m, n; printf("Input two numbers:"); scanf("%d %d", &a, &b); m = a; n = b; gcd(m, n); printf("gcd(%d, %d) = %d\n", a, b, result); return 0; }
実行結果は次のとおり。
Input two numbers:12 18 gcd(12, 18) = 6
階乗と同じく、実行ファイルをgdbでdisassembleしたものと最初にgcdが呼ばれた直後のスタックの内容が以下です。
main
(gdb) disas main Dump of assembler code for function main: 0x08048463 <+0>: push %ebp 0x08048464 <+1>: mov %esp,%ebp 0x08048466 <+3>: and $0xfffffff0,%esp 0x08048469 <+6>: push %ebx 0x0804846a <+7>: sub $0x3c,%esp 0x0804846d <+10>: mov $0x80485b0,%eax 0x08048472 <+15>: mov %eax,(%esp) 0x08048475 <+18>: call 0x8048338 <printf@plt> 0x0804847a <+23>: mov $0x80485c3,%eax 0x0804847f <+28>: lea 0x1c(%esp),%edx 0x08048483 <+32>: mov %edx,0x8(%esp) 0x08048487 <+36>: lea 0x20(%esp),%edx 0x0804848b <+40>: mov %edx,0x4(%esp) 0x0804848f <+44>: mov %eax,(%esp) 0x08048492 <+47>: call 0x8048348 <__isoc99_scanf@plt> 0x08048497 <+52>: mov 0x20(%esp),%eax 0x0804849b <+56>: mov %eax,0x2c(%esp) 0x0804849f <+60>: mov 0x1c(%esp),%eax 0x080484a3 <+64>: mov %eax,0x28(%esp) 0x080484a7 <+68>: mov 0x28(%esp),%eax 0x080484ab <+72>: mov %eax,0x4(%esp) 0x080484af <+76>: mov 0x2c(%esp),%eax 0x080484b3 <+80>: mov %eax,(%esp) 0x080484b6 <+83>: call 0x8048414 <gcd> 0x080484bb <+88>: mov 0x1c(%esp),%ecx 0x080484bf <+92>: mov 0x20(%esp),%edx 0x080484c3 <+96>: mov $0x80485c9,%eax 0x080484c8 <+101>: mov 0x24(%esp),%ebx 0x080484cc <+105>: mov %ebx,0xc(%esp) 0x080484d0 <+109>: mov %ecx,0x8(%esp) 0x080484d4 <+113>: mov %edx,0x4(%esp) 0x080484d8 <+117>: mov %eax,(%esp) 0x080484db <+120>: call 0x8048338 <printf@plt> 0x080484e0 <+125>: mov $0x0,%eax 0x080484e5 <+130>: add $0x3c,%esp 0x080484e8 <+133>: pop %ebx 0x080484e9 <+134>: mov %ebp,%esp 0x080484eb <+136>: pop %ebp 0x080484ec <+137>: ret End of assembler dump.
gcd
(gdb) disas gcd Dump of assembler code for function gcd: 0x08048414 <+0>: push %ebp 0x08048415 <+1>: mov %esp,%ebp 0x08048417 <+3>: sub $0x10,%esp 0x0804841a <+6>: mov 0x8(%ebp),%eax 0x0804841d <+9>: cmp 0xc(%ebp),%eax 0x08048420 <+12>: jge 0x8048434 <gcd+32> 0x08048422 <+14>: mov 0xc(%ebp),%eax 0x08048425 <+17>: xor %eax,0x8(%ebp) 0x08048428 <+20>: mov 0x8(%ebp),%eax 0x0804842b <+23>: xor %eax,0xc(%ebp) 0x0804842e <+26>: mov 0xc(%ebp),%eax 0x08048431 <+29>: xor %eax,0x8(%ebp) 0x08048434 <+32>: cmpl $0x0,0xc(%ebp) 0x08048438 <+36>: jg 0x8048442 <gcd+46> 0x0804843a <+38>: mov 0x8(%ebp),%eax 0x0804843d <+41>: mov %eax,0x2c(%ebp) 0x08048440 <+44>: jmp 0x8048461 <gcd+77> 0x08048442 <+46>: mov 0x4(%ebp),%eax 0x08048445 <+49>: sub $0x14,%eax 0x08048448 <+52>: mov %eax,0x4(%ebp) 0x0804844b <+55>: mov 0x8(%ebp),%eax 0x0804844e <+58>: mov %eax,%edx 0x08048450 <+60>: sar $0x1f,%edx 0x08048453 <+63>: idivl 0xc(%ebp) 0x08048456 <+66>: mov %edx,%eax 0x08048458 <+68>: mov %eax,0x30(%ebp) 0x0804845b <+71>: mov 0xc(%ebp),%eax 0x0804845e <+74>: mov %eax,0x34(%ebp) 0x08048461 <+77>: leave 0x08048462 <+78>: ret End of assembler dump.
最初にgcdが呼ばれた直後のスタック(入力は12 18)
(gdb) x/32x $ebp 0xbffff678: 0xbffff6c8 0x080484bb 0x0000000c 0x00000012 0xbffff688: 0xbffff69c 0x08048304 0x0028bff4 0x08049ff4 0xbffff698: 0xbffff6c8 0x00000012 0x0000000c 0x0028c324 0xbffff6a8: 0x00000012 0x0000000c 0x0015ed35 0x0011ea50 0xbffff6b8: 0x080484fb 0x0028bff4 0x080484f0 0x00000000 0xbffff6c8: 0xbffff748 0x00145e37 0x00000001 0xbffff774 0xbffff6d8: 0xbffff77c 0x0012e414 0xffffffff 0x0012cff4 0xbffff6e8: 0x0804824b 0x00000001 0xbffff730 0x0011da31
for, while, do, goto, 再帰を使わずに反復処理ができました。ちょっと反則臭いですが。