简体   繁体   English

G++ 尾递归优化失败

[英]G++ tail recursion optimization failing

I base this question on a comment I got for a previous question I asked here .我的这个问题是基于我在此处提出的上一个问题得到的评论。 A user replied I can have infinite tail recursion stacks.一位用户回答说我可以拥有无限的尾递归堆栈。 This is not what I found in practice however.然而,这不是我在实践中发现的。 To illustrate my point, take a look at my code:为了说明我的观点,看看我的代码:

#include <iostream>
#include <string>

void tail_print(string& in, size_t& index) //prints in backwards
{
  if (index == 0)
    {
      cout << '$' << endl;
      return;
    }

  cout << in[index];
  index--;
  tail_print(in, index);
}

int main()
{
  string a("abc$");
  size_t pos = a.length()-1;
  tail_print(a, pos);
  return 0;
}

Lets say the input string in contains characters in between the range: 1<in.length()<1000000.假设输入字符串in包含范围之间的字符:1<in.length()<1000000。

The code is compiled with: g++ -pipe -std=c++14 -O2 $file -lm -o exe代码编译为: g++ -pipe -std=c++14 -O2 $file -lm -o exe

This throws signal 11 (SIGSEG).这将引发信号 11 (SIGSEG)。 I can't precisely tell at which input this fails, but I can tell with some certainty that the cause of this signal is from within this subroutine (works fine if I print the characters backwards with a for loop).我无法准确判断哪个输入失败,但我可以肯定地说这个信号的原因来自这个子程序(如果我用 for 循环向后打印字符,效果很好)。 Please note that this is part of a larger program so there may be unforeseeable complications (slim chance).请注意,这是一个更大计划的一部分,因此可能会出现不可预见的并发症(机会很小)。 In either way, I have to cast some doubt about tail recursion optimization if it causes a stackoverflow at a measely O(million) depth.无论哪种方式,如果尾递归优化导致堆栈溢出的深度仅为 O(百万),我必须对此表示怀疑。

I'm using the following g++ version:我正在使用以下 g++ 版本:

~$ g++ --version
g++ (Ubuntu 5.5.0-12ubuntu1~16.04) 5.5.0 20171010

If you rely on tail recursion you are at the mercy of the compiler as to whether it chooses to optimise your code.如果您依赖尾递归,那么编译器是否会选择优化您的代码,您将受到编译器的支配。 Debug builds will not be optimised so will always fail.调试构建不会被优化,所以总是会失败。

In your case printing a single character through std::cout seems to be the cause of your issue.在您的情况下,通过std::cout打印单个字符似乎是您的问题的原因。 libstdc++ seems to implement printing a single character via a call to: libstdc++ 似乎通过调用来实现打印单个字符:

std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

For some reason this seems to trip up the tail recursion optimisation before GCC 10. All versions of Clang fail to optimise this too.出于某种原因,这似乎在 GCC 10 之前触发了尾递归优化。所有版本的 Clang 也未能对此进行优化。

Replacing cout << in[index] with std::cout.put(in[index]) seems to allow all versions of GCC (down to 4.1.2 at least) and Clang to optimise the tail recursion: https://godbolt.org/z/Th1bT8 Replacing cout << in[index] with std::cout.put(in[index]) seems to allow all versions of GCC (down to 4.1.2 at least) and Clang to optimise the tail recursion: https://godbolt .org/z/Th1bT8

Interestingly calling std::__ostream_insert directly also works (but don't do that as you are then relying on internal libstdc++ implementation details): https://godbolt.org/z/9M5xd4有趣的是,直接调用std::__ostream_insert也可以(但不要这样做,因为您依赖于内部 libstdc++ 实现细节): https://godbolt.org/z/9M5xd4

I think through the various levels of function call in libstdc++ you end up with (due to the function char argument being taken by value):我认为通过libstdc++中 function 调用的各个级别,您最终会得到(由于 function char参数被取值):

char c = in[index];
std::__ostream_insert<char, std::char_traits<char> >(std::cout, &c, 1);

Creating a pointer to a local variable seems to be what prevents the tail recursion: https://godbolt.org/z/KM4jGY , presumably this is because the compiler can't know what the called function will do with that pointer so it can't guarantee that using a loop will have the same behaviour.创建指向局部变量的指针似乎是防止尾递归的原因: https://godbolt.org/z/KM4jGY ,大概这是因为编译器不知道调用的 function 将使用该指针做什么,所以它可以'不保证使用循环将具有相同的行为。

As all tail recursions should be trivially replaceable with a loop its best to not rely on the vagaries of the compiler to do it for you, this will then work even in an unoptimised build:由于所有尾递归都应该可以用循环轻松替换,因此最好不要依赖编译器的变幻莫测来为您执行此操作,即使在未优化的构建中也可以使用:

void tail_print(const std::string& in, size_t index) //prints in backwards
{
    for (size_t i = index; i > 0; i--)
    {
        std::cout << in[i];
    }
    std::cout << "$\n";
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM