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); }
通过clang编译(使用-O1)生成以下ASM: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
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
问题:
{lambda()#1}
是什么?据我所知,它可能是封装函数对象(即lambda body)的闭包。
请确认是否有compute()
时都会生成一个新的闭包?还是同一个例子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