gcc 是不是优化递归函数?怎么做? [关闭]

Posted

技术标签:

【中文标题】gcc 是不是优化递归函数?怎么做? [关闭]【英文标题】:Does gcc optimize recursive functions? How to do it? [closed]gcc 是否优化递归函数?怎么做? [关闭] 【发布时间】:2013-04-07 14:21:09 【问题描述】:

我今天发现了一个关于 gcc http://ridiculousfish.com/blog/posts/will-it-optimize.html 的有趣测验

这段代码怎么来的

int factorial(int x) 
   if (x > 1) return x * factorial(x-1);
   else return 1;

可以被编译器翻译成

int factorial(int x) 
   int result = 1;
   while (x > 1) result *= x--;
   return result;

这是真的吗? gcc是怎么做到的?

【问题讨论】:

我不太明白你在问什么问题(与阶乘有关) gcc 如何将非尾递归函数转换为循环。 一题=一题,为什么要二合一问? -.-" 我同意一个问题。如果这个问题被重新打开,我建议删除 strlen() 部分并将其作为一个单独的问题提出。 【参考方案1】:

您已经知道 gcc 可以将尾递归函数优化为循环。 gcc 可以做的另一件事(在您的链接中提到)是尝试将非尾递归函数优化为尾递归函数。

你的阶乘函数在这里:

int factorial(int x) 
   if (x > 1) return x * factorial(x-1);
   else return 1;

现在我将尝试进行尽可能少的更改并将其重写为尾递归。首先,我将翻转 if 测试:

int factorial(int x) 
   if (!(x > 1)) return 1;
   else return x * factorial(x-1);

接下来,我将删除不需要的else

int factorial(int x) 
   if (!(x > 1)) return 1;
   return x * factorial(x-1);

这几乎是尾递归的,但它返回x * factorial() 而不仅仅是factorial()。使这个尾递归的典型方法是包含第二个参数,它是一个累加器。

int factorial(int x, int accumulator = 1) 
   if (!(x > 1)) return accumulator;
   return factorial(x - 1, x * accumulator);

现在这是一个尾递归函数,可以优化成循环。

【讨论】:

这不是尾递归,但... int factorial(int x, int result) if (x > 1) return factorial(x - 1, result * x); return result; // 现在它是尾递归的,因为在factorial 的下一个条目之外不需要xresult,所以gcc 可以从递归进入阶乘之前的堆栈。 好点。我会修改我的答案以提及这一点,但我想我会修改你的答案并等待它被接受。 现在更新了,因为我真的很讨厌留下错误的答案,但我喜欢简单的流程而不是你更技术性的答案。【参考方案2】:

编译器可以通过将乘法放在递归函数调用之前将该代码转换为可优化的尾部调用:

int factorial(int x) 
    return factorial_tail_call(x, 1);


int factorial_tail_call(int x, int result) 
    if (x > 1) return factorial_tail_call(x-1, result*x);
    return result;

通过在递归调用factorial_tail_call 之前执行result*x 的评估,编译器可以确定不再需要xresult。因此,它可以将它们从堆栈中弹出。这形成了堆栈不需要需要增长的证据。

你能看出转换后的代码之间有什么相似之处吗? 1 在同一个地方,条件x > 1 在同一个地方,return result; 在同一个地方。提供编译器实现尾调用优化,这只是表达相同算法的不同方式。通过将乘法表达式移动到一个参数中并将您帖子中的代码放入 cmets 到右侧,您可能会看到一些功能相似之处,以及编译器如何设法进行其余的转换:

int factorial(int x) 
    return factorial_tail_call(x, 1);                     // int result = 1;


int factorial_tail_call(int x, int result) 
    if (x > 1) return factorial_tail_call(x-1, result*x); // while (x > 1) result *= x--;
    return result;                                        // return result;

n1570.pdf 的§5.1.2.3p4

在抽象机中,所有表达式都按照 语义。一个实际的实现不需要评估一个 表达式,如果它可以推断出它的值没有被使用并且没有 产生了所需的副作用(包括任何由调用 函数或访问 volatile 对象)。

编译器是聪明的东西,由比我们大多数人优秀得多的程序员编写。如果编译器可以确定两段代码是等价的,那么它可以选择它希望的两者中的任何一个(有一些限制,在下面的引用中描述)。例如,它可以用单个printf 表达式替换计算和打印前一千个素数的循环。

n1570.pdf 的§5.1.2.3p6

对一致性实现的最低要求是:

——对 volatile 对象的访问严格按照 抽象机的规则。

——程序终止时,所有写入文件的数据应 与执行程序的结果相同 会产生抽象语义。

——交互设备的输入和输出动态应取 按照 7.21.3 的规定放置。这些要求的目的是 无缓冲或行缓冲的输出会尽快出现,以 确保提示消息实际出现在程序之前 等待输入。

这是程序的可观察行为。

这就是为什么微优化是徒劳的原因之一。

如果另一个线程修改了 strlen 正在处理的字符串,那就是竞争条件。竞争条件是未定义的行为。您需要使用互斥锁来保护字符串以确保不会发生这种情况,或者学习更好的多线程范例。你在看哪本书?

n1570.pdf 的§5.1.2.4p25

一个程序的执行包含一个数据竞争,如果它包含两个 不同线程中的冲突操作,至少其中一个不是 原子的,并且两者都不发生在另一个之前。任何此类数据竞赛 导致未定义的行为。

【讨论】:

“好得多的程序员”不一定公平...编译器是由专门从事编译器编写的程序员编写的! 是的,他们知道 C 标准。他们知道什么是明确定义的,什么不是,比大多数没有编写 C 编译器的人要好得多。 @OliCharlesworth 我修改了概括性:比我们/比我们大多数人/认识到我们中的一些人是体面的程序员,无论这看起来多么罕见。 没关系,我并不是说我是一个比 GCC 作者更好的程序员,或者类似的东西 ;) 更重要的是,编译器编写只是一种特殊的专业,无论它们多么神奇好像…… @OliCharlesworth 我不建议这样做。他们非常擅长用多种语言进行编程,以至于他们知道如何编写一些将一种语言翻译成另一种语言的自动机(希望通过规范)。他们也可能足够熟练,知道如何以可以公开未来优化的方式表达这种翻译,但这来自于学习数据结构和算法等通用编程知识。我可能应该在这个答案中提到这一点。

以上是关于gcc 是不是优化递归函数?怎么做? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何启用递归函数以避免堆栈溢出?

python之路——递归函数

如何解决栈溢出

Scala尾递归优化

python语法

Oz 中的尾递归优化