这个尾部是递归的,是否会导致堆栈溢出? C#.Net

时间:2016-06-11 22:01:59

标签: c# .net stack-overflow tail-recursion

我想知道为什么我得到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;
    }

所以我的问题是;是第二个不被认为是尾递归的,即它不能创建堆栈溢出,因为它不会产生所有堆栈调用?

2 个答案:

答案 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,您会发现此处可能存在堆栈溢出。