関数再帰とスタックの関係
まず,逆アセンブリ言語により,最も簡単な再帰関数とスタックの関係を理解する.
逆アセンブリ言語を取得する方法は、visual studio 2008でdebug環境でdebug/windows/disassemblyで逆アセンブリ後の言語を表示できます.階乗nを見てみましょう!の実装
そのC言語実装コードは以下の通りである.
その逆アセンブリの後の言語は以下の通りです.
メインプログラムmain
そのfactorial関数のアセンブリは以下の通りです.
アセンブリ全体で、
factorialの逆アセンブリでは、
push eax;スタックに入るたびのパラメータをeaxレジスタに保存し、n!=1の場合、毎回のパラメータがスタックに入ります.
実際には、関数呼び出しにおけるスタックフレームの一連の動作に関連しています.http://blog.csdn.net/free2011/article/details/6868334このブログでは、関数を呼び出すときのスタックフレームの一連の操作について詳しく説明しています.
次のようにまとめます.
関数再帰は、システム内のスタックを用いて動作し、スタックフレームの一連の動作によって再帰を実現する.このプロセスはシステムによって行われる.
階乗ではfactorial関数のパラメータをスタックに入れ,スタックフレームの一連の動作によりパラメータのスタックを実現し,さらに階乗という動作を完了する.プロセス全体は実際にはスタックのインスタックとアウトスタックの問題です.
次に、スタックを自分で定義することで、関数の再帰を実現します.
メインプログラム部分のみが表示されます.メインプログラムでは、まずパラメータをスタックに入れます.つまり、n、n-1、...1スタックに入ってから、スタックを出て操作します.
この文章の比較的要約は,逆アセンブリ言語における階乗の再帰実現に関する実行過程と手順を見ることで,関数再帰とスタックに対する理解を深めることができることを示したい.アセンブリ言語はちょっと難しいですが、上記のブログを読むことで、みんなが読めると信じています.
逆アセンブリ言語を取得する方法は、visual studio 2008でdebug環境でdebug/windows/disassemblyで逆アセンブリ後の言語を表示できます.階乗nを見てみましょう!の実装
そのC言語実装コードは以下の通りである.
#include <stdio.h>
int factorial(int n);
int main(void)
{
int fact;
fact = factorial(4);
printf("%d
",fact);
return 0;
}
int factorial(int n)
{
if (1 == n )
return 1;
return n * factorial(n - 1);
}
その逆アセンブリの後の言語は以下の通りです.
メインプログラムmain
int main(void)
{
00DB1FD0 push ebp
00DB1FD1 mov ebp,esp
00DB1FD3 sub esp,0CCh
00DB1FD9 push ebx
00DB1FDA push esi
00DB1FDB push edi
00DB1FDC lea edi,[ebp-0CCh]
00DB1FE2 mov ecx,33h
00DB1FE7 mov eax,0CCCCCCCCh
00DB1FEC rep stos dword ptr es:[edi]
int fact;
fact = factorial(4);
00DB1FEE push 4
00DB1FF0 call @ILT+475(_factorial) (0DB11E0h)
00DB1FF5 add esp,4
00DB1FF8 mov dword ptr [fact],eax
printf("%d
",fact);
00DB1FFB mov esi,esp
00DB1FFD mov eax,dword ptr [fact]
00DB2000 push eax
00DB2001 push offset string "%d
" (0DB5A38h)
00DB2006 call dword ptr [__imp__printf (0DB82BCh)]
00DB200C add esp,8
00DB200F cmp esi,esp
00DB2011 call @ILT+320(__RTC_CheckEsp) (0DB1145h)
return 0;
そのfactorial関数のアセンブリは以下の通りです.
int factorial(int n)
{
00DB1AF0 push ebp
00DB1AF1 mov ebp,esp
00DB1AF3 sub esp,0C0h
00DB1AF9 push ebx
00DB1AFA push esi
00DB1AFB push edi
00DB1AFC lea edi,[ebp-0C0h]
00DB1B02 mov ecx,30h
00DB1B07 mov eax,0CCCCCCCCh
00DB1B0C rep stos dword ptr es:[edi]
if (1 == n )
00DB1B0E cmp dword ptr [n],1
00DB1B12 jne factorial+2Bh (0DB1B1Bh)
return 1;
00DB1B14 mov eax,1
00DB1B19 jmp factorial+3Eh (0DB1B2Eh)
return n * factorial(n - 1);
00DB1B1B mov eax,dword ptr [n]
00DB1B1E sub eax,1
00DB1B21 push eax
00DB1B22 call @ILT+475(_factorial) (0DB11E0h)
00DB1B27 add esp,4
00DB1B2A imul eax,dword ptr [n]
}
00DB1B2E pop edi
00DB1B2F pop esi
00DB1B30 pop ebx
00DB1B31 add esp,0C0h
00DB1B37 cmp ebp,esp
00DB1B39 call @ILT+320(__RTC_CheckEsp) (0DB1145h)
00DB1B3E mov esp,ebp
00DB1B40 pop ebp
00DB1B41 ret
アセンブリ全体で、
call @ILT+475(_factorial) (0DB11E0h)
以前のpushはパラメータのスタックです.ここでは重要であり,他のpushはスタックのバランスのためにシステムが行う必要な動作と考えられる.factorialの逆アセンブリでは、
00DB1B39 call @ILT+320(__RTC_CheckEsp) (0DB1145h)
という言葉は、関数factorial呼び出し自体、すなわち再帰である.push eax;スタックに入るたびのパラメータをeaxレジスタに保存し、n!=1の場合、毎回のパラメータがスタックに入ります.
00DB1B2A imul eax,dword ptr [n]
このステップは、乗算を行うためである.eaxレジスタに乗算値を保存します.実際には、関数呼び出しにおけるスタックフレームの一連の動作に関連しています.http://blog.csdn.net/free2011/article/details/6868334このブログでは、関数を呼び出すときのスタックフレームの一連の操作について詳しく説明しています.
次のようにまとめます.
関数再帰は、システム内のスタックを用いて動作し、スタックフレームの一連の動作によって再帰を実現する.このプロセスはシステムによって行われる.
階乗ではfactorial関数のパラメータをスタックに入れ,スタックフレームの一連の動作によりパラメータのスタックを実現し,さらに階乗という動作を完了する.プロセス全体は実際にはスタックのインスタックとアウトスタックの問題です.
次に、スタックを自分で定義することで、関数の再帰を実現します.
#include "stack.h"
#define NumOfStack 10
int main(void)
{
StackNode * pStackNode = NULL ;
int NOfFact;
int tmp = 1,Sum = 1;
pStackNode = CreateStack(NumOfStack);
printf("the number of Factorial
");
scanf("%d",&NOfFact);
while(NOfFact)
{
Push(pStackNode,NOfFact--);
}
while(pStackNode->top)
{
Pop(pStackNode,&tmp);
Sum *= tmp;
}
printf("sum is %d
",Sum);
return 0;
}
メインプログラム部分のみが表示されます.メインプログラムでは、まずパラメータをスタックに入れます.つまり、n、n-1、...1スタックに入ってから、スタックを出て操作します.
この文章の比較的要約は,逆アセンブリ言語における階乗の再帰実現に関する実行過程と手順を見ることで,関数再帰とスタックに対する理解を深めることができることを示したい.アセンブリ言語はちょっと難しいですが、上記のブログを読むことで、みんなが読めると信じています.