Because for and foreach are the products of imperative languages, recursion is widely used in functional programming, but the problem with recursion is that it will cause stack overflow, to solve this problem, we have re-designed a recursive algorithm-tail recursion. We can use this algorithm to convert CALL commands into JMP commands during compilation, in this way, recursive calls only occupy one stack frame, but the premise is that the recursive algorithm must be transformed to conform to the standard of tail recursion. What is the standard of tail recursion?
Let's look at an example of a common recursive call:
class Program
{
static void Main(string[] args)
{
var result = FactorialNormalRecursively(5);
}
public static int FactorialNormalRecursively(int n)
{
if (n == 1)
{
return 1;
}
return FactorialNormalRecursively(n - 1) * n;
}
}
If FactorialNormalRecursively (5) is executed, the specific algorithm is as follows:
5*(4*(3*(2*(1 ))))
The problem with this algorithm is that every time we execute a recursive call and then execute the * operation, in this case, we cannot change the CALL to JMP during compilation (refer to the assembly code below ).
The transformed recursion follows the standard of tail recursion:
class Program
{
static void Main(string[] args)
{
var result = FactorialTailRecursively(5, 1);
}
public static int FactorialTailRecursively(int n, int result)
{
result = result * n;
if (n == 1)
{
return result;
}
return FactorialTailRecursively(n - 1, result);
}
}
If FactorialTailRecursively (5, 1) is executed, the specific algorithm is as follows:
(5*4) * 3) * 2) * 1)
The biggest difference between this algorithm and the previous algorithm is that it executes * computing before executing recursive calls, in this case, we can change the CALL to JMP during compilation to consume only one stack frame (refer to the assembly code below ).
The specific assembly code analysis is as follows:
// The Assembly source code is called recursively.
Int result = FactorialNormalRecursively (5 );
00000035 mov ecx, 5 [parameter n is transferred through ecx]
2017003a call FD3FB008
2017003f mov dword ptr [ebp-44h], eax [returned through eax]
00000042 mov eax, dword ptr [ebp-44h]
00000045 mov dword ptr [ebp-40h], eax [Save R value]
Public static int FactorialNormalRecursively (int n)
{
00000000 push ebp
00000001 mov ebp, esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp, 3Ch
00000009 mov esi, ecx
10000000b lea edi, [ebp-38h]
0000000e mov ecx, 0Bh
00000013 xor eax, eax
00000015 rep stos dword ptr es: [edi]
00000017 mov ecx, esi
00000019 xor eax, eax
2017001b mov dword ptr [ebp-1Ch], eax
2017001e mov dword ptr [ebp-3Ch], ecx
00000021 cmp dword ptr ds: [009D9134h], 0
00000028 je 0000002F
2017002a call 76AE9109
2017002f xor edx, edx
00000031 mov dword ptr [ebp-40h], edx
00000034 mov dword ptr [ebp-44h], 0
10000003b nop
If (n = 1)
2017003c cmp dword ptr [ebp-3Ch], 1
00000040 setne al
00000043 movzx eax, al
00000046 mov dword ptr [ebp-44h], eax
00000049 cmp dword ptr [ebp-44h], 0
0000004d jne 0000005A
{
0000004f nop
Return 1;
00000050 mov dword ptr [ebp-40h], 1
00000057 nop
00000058 jmp 00000073
}
Return FactorialNormalRecursively (n-1) * n;
2017005a mov ecx, dword ptr [ebp-3Ch]
2017005d dec ecx
2017005e call FD3FAF98 [common recursion has some code after the Call itself, which is the key to transformation to tail recursion]
00000063 mov dword ptr [ebp-48h], eax
00000066 mov eax, dword ptr [ebp-3Ch]
00000069 imul eax, dword ptr [ebp-48h]
2017006d mov dword ptr [ebp-40h], eax
00000070 nop
00000071 jmp 00000073
}
00000073 mov eax, dword ptr [ebp-40h]
00000076 lea esp, [ebp-0Ch]
00000079 pop ebx
2017007a pop esi
2017007b pop edi
0000007c pop ebp
2017007d ret
// The source code of the Assembly is recursively called.
Int result = FactorialTailRecursively (5, 1 );
00000035 mov edx, 1 [pass the result through edx]
10000003a mov ecx, 5 [parameter n passed through ecx]
2017003f call FD3FB008
00000044 mov dword ptr [ebp-44h], eax [results returned through eax]
00000047 mov eax, dword ptr [ebp-44h]
2017004a mov dword ptr [ebp-40h], eax [Save R value]
Public static int FactorialTailRecursively (int n, int result)
{
00000000 push ebp
00000001 mov ebp, esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp, 40 h
00000009 mov esi, ecx
10000000b lea edi, [ebp-38h]
0000000e mov ecx, 0Bh
00000013 xor eax, eax
00000015 rep stos dword ptr es: [edi]
00000017 mov ecx, esi
00000019 xor eax, eax
2017001b mov dword ptr [ebp-1Ch], eax
2017001e mov dword ptr [ebp-3Ch], ecx
00000021 mov dword ptr [ebp-40h], edx
00000024 cmp dword ptr ds: [009D9134h], 0
2017002b je 00000032
2017002d call 76AE9111
00000032 xor edx, edx
00000034 mov dword ptr [ebp-44h], edx
00000037 mov dword ptr [ebp-48h], 0
2017003e nop
Result = result * n;
2017003f mov eax, dword ptr [ebp-3Ch]
00000042 imul eax, dword ptr [ebp-40h]
00000046 mov dword ptr [ebp-40h], eax
If (n = 1)
00000049 cmp dword ptr [ebp-3Ch], 1
2017004d setne al
00000050 movzx eax, al
00000053 mov dword ptr [ebp-48h], eax
00000056 cmp dword ptr [ebp-48h], 0
2017005a jne 00000066
{
2017005c nop
Return result;
2017005d mov eax, dword ptr [ebp-40h]
00000060 mov dword ptr [ebp-44h], eax
00000063 nop
00000064 jmp 0000007E
}
Return FactorialTailRecursively (n-1, result );
00000066 mov ecx, dword ptr [ebp-3Ch]
00000069 dec ecx
2017006a mov edx, dword ptr [ebp-40h] [mov dword ptr [ebp-3Ch], ecx]
2017006d call FD3FAFA0 [jmp 0000003f]
00000072 mov dword ptr [ebp-4Ch], eax [Remove the line]
00000075 mov eax, dword ptr [ebp-4Ch] [Remove the line]
00000078 mov dword ptr [ebp-44h], eax [Remove the line]
10000007b nop [Remove the line]
0000007c jmp 0000007E [Remove the line]
}
2017007e mov eax, dword ptr [ebp-44h]
00000081 lea esp, [ebp-0Ch]
00000084 pop ebx
00000085 pop esi
00000086 pop edi
00000087 pop ebp
00000088 ret