我想知道为什么我得到VS / Resharper的第一种方式注意尾递归可以用循环替换,我确实设法得到类似的东西导致堆栈溢出所以我读了尾递归的方法工作以及它如何增长堆栈等。
第一个生成尾递归注释。
private static string GetLine()
{
string s = Console.ReadLine();
if (s == null)
{
return GetLine();
}
return s;
}
但是这样做不会:
private static string GetLine()
{
string s = Console.ReadLine();
if (s == null)
{
s = GetLine();
}
return s;
}
所以我的问题是;是第二个不被认为是尾递归的,即它不能创建堆栈溢出,因为它不会产生所有堆栈调用?
答案 0 :(得分:4)
正如我们在his answer中解释的那样,ReSharper试图在代码中找到可以为其提供重构的已知模式。
但是,让我们来看看这两种情况的生成代码。
第一个功能:
private static string GetLineA()
{
string s = Console.ReadLine();
if (s == null)
{
return GetLineA();
}
return s;
}
给出(x64,发布):
00007FFB34AC43EE add byte ptr [rax],al
00007FFB34AC43F0 sub rsp,28h
00007FFB34AC43F4 call 00007FFB8E56F530 // <-- Console.ReadLine
00007FFB34AC43F9 test rax,rax
00007FFB34AC43FC jne 00007FFB34AC440F
00007FFB34AC43FE mov rax,7FFB34AC0F60h
00007FFB34AC4408 add rsp,28h
00007FFB34AC440C jmp rax
00007FFB34AC440F add rsp,28h
00007FFB34AC4413 ret
你可以清楚地看到它的尾递归,因为唯一的call
指令适用于Console.ReadLine
。
第二个版本:
private static string GetLineB()
{
string s = Console.ReadLine();
if (s == null)
{
s = GetLineB();
}
return s;
}
给出这个:
00007FFB34AC44CE add byte ptr [rax],al
00007FFB34AC44D0 sub rsp,28h
00007FFB34AC44D4 call 00007FFB8E56F530 // <-- Console.ReadLine
00007FFB34AC44D9 test rax,rax
00007FFB34AC44DC jne 00007FFB34AC44E3
00007FFB34AC44DE call 00007FFB34AC0F68 // <-- Not good.
00007FFB34AC44E3 nop
00007FFB34AC44E4 add rsp,28h
00007FFB34AC44E8 ret
那里有第二个call
,所以你不会得到尾递归,而堆栈会增长,如果它变得很大,最终会导致堆栈溢出够了。
好吧,看起来JIT并没有将代码优化为尾递归调用。
无论如何,要小心,因为你受到了JIT的支配。
此处&#39; x86中的GetLineA
:
00F32DCA in al,dx
00F32DCB call 72A209DC // <-- Console.ReadLine
00F32DD0 test eax,eax
00F32DD2 jne 00F32DDC
00F32DD4 call dword ptr ds:[12B8E94h] // <-- Ouch
00F32DDA pop ebp
00F32DDB ret
00F32DDC pop ebp
00F32DDD ret
请参阅?你无法真正依赖它,语言也不能保证。
答案 1 :(得分:1)
Resharper只是没有检测到第二种形式。它并不努力。程序分析一般很难而且不可能(参见暂停问题)。 Resharper主要有一些很好的和有用的启发式方法。
如果您将Dictionary<Color, int> ColorAndQuantities;
替换为ReadLine
,您会发现此处可能存在堆栈溢出。