我有以下初始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
问题:
- ASM中出现的
{lambda()#1}
是什么?据我所知,它可能是封装函数对象(即 lambda 体)的闭包。 如果是这样,请确认。 - 每次触发
compute()
时是否都会生成新的闭包?还是同一个实例?
-
是的,调用lambda函数需要生成闭包[除非编译器可以推断它实际上没有被使用]
-
通过此优化,每次调用都将是对
compute
的调用,而 又调用内部函数get()
,这是compute
函数中的 lambda 函数。让编译器优化到更高的程度,对于这种情况,将优化调用 - 在我的尝试中,它将完全删除整个调用-O2
,并返回预先计算的常量 847 - 正如您所期望的那样。对于更复杂的情况,它可能会也可能不会内联 lambda 部分,但保留外部调用,反之亦然。这在很大程度上取决于所涉及的功能内部所发生事件的确切细节。需要明确的是,编译器正在执行您要求的操作:调用函数
compute
,而函数又调用函数get
。
添加
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
生成的代码
- 它是您在
compute
中声明的 lambda 函数的主体(实现)。
是的, - 每次调用计算时,无论是在概念上还是在实践中(在此优化级别1),都会在堆栈上创建一个新的闭包,并使用指向该闭包的指针调用关联的 lambda 函数(作为
rdi
传递,即作为第一个参数,方式与指向成员函数的this
相同)。
1"在此优化级别"部分非常重要。这里没有任何内容实际上需要编译器生成闭包或单独的 lambda 函数。例如,在-O2
,clang 优化了所有这些,并直接在main()
中将答案作为常量返回。GCC 即使在-O1
.