11

我正在使用 gcc 4.6.1,并且得到了一些涉及调用constexpr函数的有趣行为。该程序运行良好,并立即打印出来12200160415121876738

#include <iostream>

extern const unsigned long joe;

constexpr unsigned long fib(unsigned long int x)
{
   return (x <= 1) ? 1 : (fib(x - 1) + fib(x - 2));
}

const unsigned long joe = fib(92);

int main()
{
   ::std::cout << "Here I am!\n";
   ::std::cout << joe << '\n';
   return 0;
}

这个程序需要永远运行,我从来没有耐心等待它打印出一个值:

#include <iostream>

constexpr unsigned long fib(unsigned long int x)
{
   return (x <= 1) ? 1 : (fib(x - 1) + fib(x - 2));
}

int main()
{
   ::std::cout << "Here I am!\n";
   ::std::cout << fib(92) << '\n';
   return 0;
}

为什么会有如此巨大的差异?我在第二个程序中做错了吗?

编辑:我在g++ -std=c++0x -O364 位平台上编译它。

4

4 回答 4

14

joe是一个积分常数表达式;它必须在数组范围内可用。出于这个原因,一个合理的编译器会在编译时评估它。

在您的第二个程序中,即使编译器可以在编译时计算它,也没有理由必须这样做。

于 2011-08-15T13:04:07.413 回答
4

我最好的猜测是在编译时对第一个程序进行了 fib(92) 评估,编译器有很多表格和东西来跟踪已经评估过的值......使得运行程序几乎是微不足道的,

第二个版本实际上是在运行时评估的,没有评估常量表达式的查找表,这意味着 fib(92) 的评估会产生类似 2**92 的递归调用。

换句话说,编译器没有优化 fib(92) 是一个常量表达式这一事实。

于 2011-08-15T13:06:07.907 回答
2

如果编译器认为某些东西“太复杂”,那么编译器就有回旋余地来决定不在编译时进行评估。那是在没有绝对强制执行评估以生成可以实际运行的正确程序的情况下(正如@MSalters 指出的那样)。

我认为影响编译时惰性的决定可能是递归深度限制。(这在规范中建议为 512,但如果您愿意,您可以使用命令行标志-fconstexpr-depth来提升它。)但是,这将控制它在任何情况下都放弃......即使编译时间常数是必要的运行程序。所以对你的情况没有影响。

似乎如果您希望在代码中保证它将进行优化,那么您已经找到了一种技术。但如果constexpr-depth帮不上忙,我不确定是否有任何相关的编译器标志,否则......

于 2011-08-15T13:20:19.020 回答
2

我还想看看 gcc 是如何优化这个新的 constexpr 关键字的代码的,实际上这只是因为你调用 fib(92) 作为 ofstream::operator<< 的参数

::std::cout << fib(92) << '\n';

如果您尝试不将其作为另一个函数的参数调用(就像您在其中所做的那样),则不会在编译时对其进行评估

const unsigned long joe = fib(92);

它是在编译时评估的,如果您想了解更多信息,我写了一篇关于此的博客文章,我不知道是否应该向 gcc 开发人员提及。

于 2012-03-09T10:14:33.810 回答