C ++中lambda和常规函数的不同堆栈深度?

Posted

技术标签:

【中文标题】C ++中lambda和常规函数的不同堆栈深度?【英文标题】:Different stack depth for lambdas and regular functions in C++? 【发布时间】:2017-02-03 16:57:54 【问题描述】:

考虑一个普通的递归函数:

#include <iostream>
#include <functional>

void f(unsigned long long int x) 
    std::cout << x << "\n";
    if(x < 1e9)
        f(x+1);


int main() 
    f(1);
    return 0;

这终止于 43033

现在考虑一个递归 lambda:

#include <iostream>
#include <functional>

int main() 
    std::function<void(int)> g = [&g](unsigned long long int x) 
        std::cout << x << "\n";
        if(x < 1e9)
            g(x+1);
    ;
    g(1);
    return 0;

这会在 11736 的堆栈深度低得多时终止。

为什么 lambda 的最大堆栈深度较低?

(使用g++ (GCC) 5.4.0编译,使用-std=c++14 -Wall

另请注意,使用-O3 优化进行编译允许几乎无限递归深度,但 lambda 仍终止于 25k


编辑:在@Yakk 之后,这里是Y-combinator 的结果:

#include <iostream>
#include <functional>

using namespace std;

template <typename T, typename R>
function<R(T)> Y(function<function<R(T)>(function<R(T)>)> f) 
    // Y f = f (λx.(Y f) x)
    return f([=](T x)  return Y(f)(x); );


int main() 
    using fg = function<void(int)>;
    function<fg(fg)> sg = [](fg g) 
        return [g](unsigned long long int x) 
            std::cout << x << "\n";
            if(x < 1e9)
                g(x+1);
        ;
    ;

    Y(sg)(1);
    return 0;

这在 47819221 分别有和没有-O3 终止。

【问题讨论】:

@DieterLücking 详细说明? 尝试直接使用 lambda (auto f) 我认为开销在std::function @Motti 它不会编译,因为它需要事先知道函数签名才能递归! @prakharsingh95,好点,反正@Yakk 说开销在std::function 中,而不是在 lambda 中。 栈可以容纳 N 个字节。您可以在堆栈中放入 M 个小部件或 K 个小工具。小部件和小工具占用不同的字节数。又是什么问题? 【参考方案1】:

std 函数与 lambda 的含义不同。 std 函数是一个能够存储一些 lambda 表达式的对象,或者一个函数指针,或者一个指向成员函数的指针,或者一个指向成员数据的指针,或者几乎任何兼容地覆盖 operator() 的对象。

在 std 函数中存储 lambda 时,会产生一些开销。不多,但有一些。其中一些开销可能表现为更多地使用堆栈(并且在未优化的构建中开销会更大)。

您可以通过使用 y combinator 更直接地使用 lambda 进行递归,但即使在那里,您也会将对自身的引用作为参数传递,除非优化器消除了递归,否则它可能会使用更多堆。 (经过高度调整的优化器可能会注意到可以消除无状态 lambda 引用参数,但这似乎很难解决)。

【讨论】:

AFAIK, std::function 是编写递归 lambda 的唯一方法。如果你能举个例子那就太好了! std::function 调用有一个额外的参数:Effectively does INVOKE(f, std::forward&lt;Args&gt;(args)..., R)(引用自en.cppreference.com/w/cpp/utility/functional/function/…) @underscore_d 我也添加了结果。我还是不清楚。 @prak 链接到堆栈溢出时添加的 lambda 的许多 y 组合器之一。有很多变化。同样,不需要使用std::functionstd::function 用于类型擦除,我们所做的任何事情都不需要类型擦除。无论如何你问为什么递归深度更短,现在应该清楚原因了吧?

以上是关于C ++中lambda和常规函数的不同堆栈深度?的主要内容,如果未能解决你的问题,请参考以下文章

C 语言编程 — 堆栈与内存管理。看看

C 语言编程 — 堆栈与内存管理。看看

C语言中函数和函数体的区别是啥?

堆栈动态和堆栈动态数组

使用寄存器而不是堆栈从 x64 程序集调用 C 函数

如何减少 C 中深度递归函数的堆栈帧?