C++;Lambda生成的ASM代码 我有下面的C++代码: class Lambda { public: int compute(int &value){ auto get = [&value]() -> int { return 11 * value; }; return get(); } }; int main(){ Lambda lambda; int value = 77; return lambda.compute(value); }

C++;Lambda生成的ASM代码 我有下面的C++代码: class Lambda { public: int compute(int &value){ auto get = [&value]() -> int { return 11 * value; }; return get(); } }; int main(){ Lambda lambda; int value = 77; return lambda.compute(value); },c++,assembly,lambda,clang,C++,Assembly,Lambda,Clang,通过clang编译(使用-O1)生成以下ASM: main: # @main push rax mov dword ptr [rsp + 4], 77 mov rdi, rsp lea rsi, [rsp + 4] call Lambda::compute(int&) pop rcx ret Lambda::compute(int&): # @Lambda::compute(int&) push rax mov qword ptr [r

通过clang编译(使用-O1)生成以下ASM:

main: # @main
  push rax
  mov dword ptr [rsp + 4], 77
  mov rdi, rsp
  lea rsi, [rsp + 4]
  call Lambda::compute(int&)
  pop rcx
  ret
Lambda::compute(int&): # @Lambda::compute(int&)
  push rax
  mov qword ptr [rsp], rsi
  mov rdi, rsp
  call Lambda::compute(int&)::{lambda()#1}::operator()() const
  pop rcx
  ret
Lambda::compute(int&)::{lambda()#1}::operator()() const: # @Lambda::compute(int&)::{lambda()#1}::operator()() const
  mov rax, qword ptr [rdi]
  mov eax, dword ptr [rax]
  lea ecx, [rax + 4*rax]
  lea eax, [rax + 2*rcx]
  ret
问题:

  • ASM中出现的
    {lambda()#1}
    是什么?据我所知,它可能是封装函数对象(即lambda body)的闭包。 请确认是否有
  • 是否每次触发
    compute()
    时都会生成一个新的闭包?还是同一个例子
  • 它是您在
    compute
    中声明的lambda函数的主体(实现)
  • 是的,每次从概念上和实践上(在这个优化级别1)调用compute时,都会在堆栈上创建一个新的闭包,并使用指向该闭包的指针调用关联的lambda函数(作为
    rdi
    传递,即作为第一个参数,与成员函数的
    this
    指向的方式相同)

  • 1“在此优化级别”部分非常重要。这里没有任何东西真正需要编译器生成闭包或单独的lambda函数。例如,在
    -O2
    处,发出叮当声,直接在
    main()中以常量返回答案。gcc即使在
    -O1
    上也会进行同样的优化

  • 是的,调用lambda函数需要生成一个闭包[除非编译器可以推断它没有被实际使用]

  • 通过此优化,每次调用都是对
    compute
    的调用,该调用反过来调用内部函数
    get()
    ,该函数是
    compute
    函数中的lambda函数。在这种情况下,让编译器优化到更高的程度将优化调用-在我的尝试中,它将使用
    -O2
    完全删除整个调用,并返回预先计算的常数847-正如您所期望的那样。对于更复杂的情况,它可以内联lambda部分,也可以不内联lambda部分,但保留外部调用,反之亦然。这在很大程度上取决于所涉及的函数内部发生的事情的确切细节

    需要明确的是,编译器正在执行您要求的操作:调用函数
    compute
    ,该函数反过来调用函数
    get

  • 添加

        int value2 = 88;
        int tmp = lambda.compute(value2);
    
    原始问题中的
    main
    函数基本上会对生成的代码产生这种更改(在Linux上使用clang++):


    生成到

    lambda的代码不会转义该函数,因此它不是回调或任何东西。它只是一个常规函数,通过两级间接调用。(指向
    值的指针
    ,因此加载了2个值。)请注意,
    Lambda::compute(int&):{Lambda()#1}::operator()()
    是一个按需命名的名称;您可能正在使用而不是查看未过滤的
    clang-O1-S
    输出。这里没有魔法,它只是一个常规的
    调用
    指令。每次调用
    compute()
    您创建一个新的lambda并调用它时,ASM为什么要做一些不同的事情呢?如果您将lambda闭包对象本身传递给一个非内联函数,您可能会得到类似gcc对GNU C嵌套函数(它访问包含函数中的变量)所做的代码。但希望你能得到比那些讨厌的可执行堆栈更有效的东西,这些可执行堆栈是用代码为堆栈上的蹦床写指令字节。@PeterCordes我认为你永远不会得到那种疯狂。lambda是匿名对象,它们的
    操作符()
    本身就有一个隐藏的
    指针作为上下文传递。疯狂的可执行堆栈是一种不传递上下文指针的黑客攻击,这是必需的,因为嵌套函数在用作指针时,永远无法传递该指针
    
    main:                                   # @main
        pushq   %rbx
        subq    $16, %rsp
        movl    $77, 12(%rsp)
        ## new line to set value2
        movl    $88, 8(%rsp)
        movq    %rsp, %rbx
        ## New line, passing reference of `value2` to lambda.compute
        leaq    8(%rsp), %rsi
        movq    %rbx, %rdi
        ## Call lambda.compute
        callq   _ZN6Lambda7computeERi
        ## Same as before.
        leaq    12(%rsp), %rsi
        movq    %rbx, %rdi
        callq   _ZN6Lambda7computeERi
        addq    $16, %rsp
        popq    %rbx
        retq