g+中的尾部递归问题+; 我在C++中用尾递归函数搞砸了,我遇到了G++编译器的一个小问题。

g+中的尾部递归问题+; 我在C++中用尾递归函数搞砸了,我遇到了G++编译器的一个小问题。,c++,recursion,functional-programming,g++,tail-recursion,C++,Recursion,Functional Programming,G++,Tail Recursion,当numbers[]的大小超过几百个整数时,以下代码会导致堆栈溢出。检查由g++生成的汇编代码,发现twoSum\u Helper正在对自身执行递归调用指令 问题是以下哪一项导致了这种情况 下面我忽略了一个错误,它阻止了尾部递归 我在使用g++时犯了一个错误 在g++编译器中检测尾部递归函数时存在缺陷 我正在通过MinGW和g++4.5.0在WindowsVista x64上使用g++-O3-Wall-fno stack protector test.c进行编译 struct result

numbers[]
的大小超过几百个整数时,以下代码会导致堆栈溢出。检查由g++生成的汇编代码,发现twoSum\u Helper正在对自身执行递归
调用
指令

问题是以下哪一项导致了这种情况

  • 下面我忽略了一个错误,它阻止了尾部递归
  • 我在使用g++时犯了一个错误
  • 在g++编译器中检测尾部递归函数时存在缺陷
我正在通过MinGW和g++4.5.0在WindowsVista x64上使用
g++-O3-Wall-fno stack protector test.c
进行编译

struct result
{
    int i;
    int j;
    bool found;
};

struct result gen_Result(int i, int j, bool found)
{
    struct result r;
    r.i = i;
    r.j = j;
    r.found = found;
    return r;
}

// Return 2 indexes from numbers that sum up to target.
struct result twoSum_Helper(int numbers[], int size, int target, int i, int j)
{
    if (numbers[i] + numbers[j] == target)
        return gen_Result(i, j, true);
    if (i >= (size - 1))
        return gen_Result(i, j, false);
    if (j >= size)
        return twoSum_Helper(numbers, size, target, i + 1, i + 2);
    else
        return twoSum_Helper(numbers, size, target, i, j + 1);
}

由于
twoSum\u Helper
的代码正在调用自己,因此程序集显示了这种情况并不奇怪。这就是递归的全部要点:-)所以这与g++没有任何关系

每个递归都会创建一个新的堆栈帧,默认情况下,堆栈空间是有限的。您可以增加堆栈大小(不知道如何在Windows上这样做,在UNIX上使用
ulimit
命令),但这只会延迟崩溃

真正的解决方案是摆脱递归。参见示例和

试着用-O2而不是-O3编译

不管怎样,它对O2不起作用。唯一有效的方法是将
结果
对象返回到作为参数给定的引用中


但实际上,删除尾部调用并使用循环要容易得多。TCO在这里是为了优化内联或执行渐进展开时发现的尾部调用,但无论如何,在处理大值时不应该尝试使用递归。

我将看两件事

  • if语句中的返回调用将在调用后需要解决的函数当前运行的堆栈帧中为else设置分支目标(这意味着任何TCO尝试都无法覆盖正在执行的堆栈帧,从而否定TCO)

  • numbers[]数组参数是一种可变长度的数据结构,它还可以防止TCO,因为在TCO中,以某种方式使用相同的堆栈帧。如果调用是自引用的(与您的一样),那么它将用新调用的值/引用覆盖堆栈定义的变量(或本地定义的)。如果尾部调用是对另一个函数的,那么它将用新函数覆盖整个堆栈帧(在TCO可以在a=>B=>C中完成的情况下,TCO可能会使其在执行期间看起来像内存中的a=>C)。我想试试指针

  • <> p>我已经在C++中构建了任何一个月,所以我没有运行任何测试,但是我认为其中之一/两个都阻止优化。p> 我无法让g++4.4.0(在mingw下)执行尾部递归,即使是在这个简单的函数上:

    static void f (int x)
      {
      if (x == 0) return ;
      printf ("%p\n", &x) ; // or cout in C++, if you prefer
      f (x - 1) ;
      }
    

    我曾尝试过<代码> -O3, -O2 -FNO堆栈保护器< /C>,C和C++变体。没有尾部递归。

    我听到其他人抱怨,尾部递归只使用gcc优化,而不是g++。
    能否尝试使用gcc。

    尝试将代码更改为:

    // Return 2 indexes from numbers that sum up to target.
    struct result twoSum_Helper(int numbers[], int size, int target, int i, int j)
    {
        if (numbers[i] + numbers[j] == target)
            return gen_Result(i, j, true);
        if (i >= (size - 1))
            return gen_Result(i, j, false);
    
        if(j >= size)
            i++; //call by value, changing i here does not matter
        return twoSum_Helper(numbers, size, target, i, i + 1);
    }
    
    编辑:根据询问者的评论删除了不必要的参数

    // Return 2 indexes from numbers that sum up to target.
    struct result twoSum_Helper(int numbers[], int size, int target, int i)
    {
        if (numbers[i] + numbers[i+1] == target || i >= (size - 1))
            return gen_Result(i, i+1, true);
    
        if(i+1 >= size)
            i++; //call by value, changing i here does not matter
        return twoSum_Helper(numbers, size, target, i);
    }
    

    < > C或C++中的尾调用优化非常有限,而且是一个很失败的原因。原因是,通常没有安全的方法来跟踪传递指向任何局部变量的指针或引用的函数的调用(作为所讨论调用的参数,或者实际上是同一函数中的任何其他调用)——当然,这在C/C++中到处都在发生,没有它几乎是不可能的


    您看到的问题可能与此相关:GCC可能通过实际传递调用方堆栈上分配的隐藏变量的地址(被调用方将其复制到堆栈中)来编译返回的结构,这使得它属于上述场景。

    对尾部调用优化(TCO)的支持在C/C++中受到限制

    因此,如果代码依赖于TCO来避免堆栈溢出,那么最好使用循环重写它。否则,需要进行一些自动测试,以确保代码得到优化

    通常,TCO可通过以下方式抑制:

    • 将指针传递到函数的堆栈上的递归函数到外部函数(在C++的情况下也通过引用传递该对象);李>
    • 具有非平凡析构函数的本地对象,即使尾部递归有效(析构函数在尾部
      return
      语句之前调用),例如
    这里,TCO由于按值返回结构而混淆。 如果所有递归调用的结果将写入其他函数twoSum中分配的相同内存地址,则可以修复此问题(类似于对的回答)

    对于
    twoSum\u Helper
    的所有递归调用,
    res\u
    指针的值都是常量。 在汇编输出(-S标志)中可以看到,
    twoSum\u Helper
    tail递归被优化为一个循环,即使有两个递归退出点


    编译选项:g++-O2-S(g++版本4.7.2)。

    您是否已经尝试单独执行条件增量,并且仅使用增量参数执行一次递归调用?它不如你的例子好,但可能会对你的问题有所帮助。@stefaanv是的,没有用。调用似乎发生在else语句上,但再多的调整都不会导致它使用jmp而不是调用。如果使用单个语句ala
    return twoSum\u Helper(number、size、target、i+j\u ge\u size、j\u size?i+2:j+1)
    where
    j\u ge\u size
    is
    bool j>=size
    ?(适合你自己)重新隐式转换f
    struct result
    {
        int i;
        int j;
        bool found;
    };
    
    struct result gen_Result(int i, int j, bool found)
    {
        struct result r;
        r.i = i;
        r.j = j;
        r.found = found;
        return r;
    }
    
    struct result* twoSum_Helper(int numbers[], int size, int target,
        int i, int j, struct result* res_)
    {
        if (i >= (size - 1)) {
            *res_ = gen_Result(i, j, false);
            return res_;
        }
        if (numbers[i] + numbers[j] == target) {
            *res_ = gen_Result(i, j, true);
            return res_;
        }
        if (j >= size)
            return twoSum_Helper(numbers, size, target, i + 1, i + 2, res_);
        else
            return twoSum_Helper(numbers, size, target, i, j + 1, res_);
    }
    
    // Return 2 indexes from numbers that sum up to target.
    struct result twoSum(int numbers[], int size, int target)
    {
        struct result r;
        return *twoSum_Helper(numbers, size, target, 0, 1, &r);
    }