C ++ Lambda生成的ASM代码

时间:2018-03-15 07:48:31

标签: c++ assembly lambda clang

我有以下初始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);
}
使用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

问题:

  1. ASM中出现的{lambda()#1}是什么?据我所知,它可能是一个封装函数对象(即lambda体)的闭包。 请确认是否。
  2. 每次触发compute()时都会生成新的闭包吗?或者是同一个实例?

2 个答案:

答案 0 :(得分:2)

  1. 它是您在compute中声明的lambda函数的主体(实现)。
  2. 是的,每次调用计算,概念和实践(在此优化级别 1 )时,都会在堆栈上创建一个新的闭包,并使用指向该关联的lambda函数调用该关联的lambda函数closure(作为rdi传递,即作为第一个参数,与成员函数指向的this相同)。
  3. 1 "在此优化级别"部分非常重要。这里没有任何东西实际上需要编译器生成闭包或单独的lambda函数。例如,在-O2,clang optimizes all this away只会直接在main()中将答案作为常量返回。即使在-O1,gcc也会执行相同的优化。

答案 1 :(得分:2)

  1. 是的,调用lambda函数将需要生成一个闭包[除非编译器可以推断它实际上没有被使用]
  2. 通过此优化,每次调用都会调用compute,后者又会调用内部函数get(),这是compute函数中的lambda函数。让编译器进行更高程度的优化,对于这种情况,将优化呼叫 - 在我的尝试中,它将完全删除整个呼叫-O2,并且只返回预先计算的常数847 - 就像你和期待。对于更复杂的情况,它可能会或可能不会内联lambda部分但保持外部调用,反之亦然。这在很大程度上取决于所涉及功能内部的具体细节。

    为了清楚起见,编译器完全按照您的要求执行操作:调用函数compute,然后调用函数get

  3. 添加

        int value2 = 88;
        int tmp = lambda.compute(value2);
    

    进入原始问题中的main函数基本上会对生成的代码产生这种更改(在Linux上使用clang ++):

    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
    

    生成的代码