我刚刚使用C ++ 11/14 std :: thread对象编写了一个线程池,并使用了工作队列中的任务。在lambda表达式中调用递归函数时遇到了一些奇怪的行为。如果以递归方式实现fac()
(包括clang 3.5和gcc 4.9),以下代码会崩溃:
#include <functional>
#include <vector>
std::size_t fac(std::size_t x) {
// This will crash (segfault).
// if (x == 1) return 1;
// else return fac(x-1)*x;
// This, however, works fine.
auto res = 1;
for (auto i = 2; i < x; ++i) {
res *= x;
}
return res;
}
int main() {
std::vector<std::function<void()> > functions;
for (auto i = 0; i < 10; ++i) {
functions.emplace_back([i]() { fac(i); });
}
for (auto& fn : functions) {
fn();
}
return 0;
}
但是,它确实适用于上面的迭代版本。我错过了什么?
答案 0 :(得分:4)
for (auto i = 0; i < 10; ++i) {
functions.emplace_back([i]() { fac(i); });
通过该循环的第一个时间,i
将被设置为零,因此您正在执行:
fac(0);
使用递归定义:
if (x == 1) return 1;
else return fac(x-1)*x;
表示else
块将执行,因此x
将回绕到最大size_t
值(因为它是无符号的)。
然后它将从那里运行到1
,每次消耗一个堆栈帧。在最小值,将消耗65,000左右的堆栈帧(基于标准中size_t
的最小允许值),但可能更多,多更多。
导致崩溃的原因。修复相对简单。由于0!
定义为1
,因此您只需将语句更改为:
if (x <= 1)
return 1;
return fac (x-1) * x;
但是你应该记住,递归函数最适合解决方案空间快速减少的情况,一个典型的例子是二进制搜索,每次重复时解决方案空间减半
不快速减少解决方案空间的函数通常容易出现堆栈溢出问题(除非优化器可以优化递归)。如果传入一个足够大的数字,你可能仍会遇到问题,并且将两个无符号数字加在一起并没有什么不同(虽然我实际上看到它在许多月前被提出作为一个递归的例子): / p>
def addu (unsigned a, b):
if b == 0:
return a
return addu (a + 1, b - 1)
所以,在你的情况下,我会坚持使用迭代解决方案,尽管它没有错误:
auto res = 1;
for (auto i = 2; i <= x; ++i) // include the limit with <=.
res *= i; // multiply by i, not x.
答案 1 :(得分:3)
两个定义对x=0
都有不同的行为。循环将很好,因为它使用小于运算符:
auto res = 1;
for (auto i = 2; i < x; ++i) {
res *= x;
}
然而,
if (x == 1) return 1;
else return fac(x-1)*x;
在一个准无限循环中导致x == 1
为假,x-1
产生std::size_t
的最大可能值(通常为2 64 -1)。
答案 2 :(得分:1)
递归版本不会处理x == 0
的情况。
你需要:
std::size_t fac(std::size_t x) {
if (x == 1 || x == 0 ) return 1;
return fac(x-1)*x;
}
或
std::size_t fac(std::size_t x) {
if (x == 0 ) return 1;
return fac(x-1)*x;
}