本篇作为c++,从汇编底层角度深入理解带捕获的lambda如何转化为std::function
的开胃小菜
#include <iostream>
#include <functional>
int main(int args, char* argv[]) {
[](){
std::cout << "lambda 0" << std::endl;
}();
[]() -> int {
std::cout << "lambda 1" << std::endl;
return 0;
}();
[](int a = 1) -> int {
std::cout << "lambda 1" << std::endl;
return a;
}();
int a = 1, b = 2;
[a, b]() -> int {
std::cout << b << std::endl;
return a;
}();
}
编译后看一下符号,lambda
相关的只有4个,对应main
函数中的4个lambda
表达式
# g++ -std=c++20 -fno-elide-constructors -O0 -g -o main main.cpp
# readelf -s -W main |c++filt |grep lambda
13: 0000000000401176 43 FUNC LOCAL DEFAULT 14 main::{lambda()#1}::operator()() const
14: 00000000004011a2 47 FUNC LOCAL DEFAULT 14 main::{lambda()#2}::operator()() const
15: 00000000004011d2 48 FUNC LOCAL DEFAULT 14 main::{lambda(int)#3}::operator()(int) const
16: 0000000000401202 48 FUNC LOCAL DEFAULT 14 main::{lambda()#4}::operator()() const
看一下main
如何执行这些lambda
表达式的:
0000000000401232 <main>:
int main(int args, char* argv[]) {
# 申请了 0x20 大小的栈空间
401236: 48 83 ec 20 sub $0x20,%rsp
# lea 是加载地址,调用表达式时`rdi`寄存器传入了一个地址
401241: 48 8d 45 f5 lea -0xb(%rbp),%rax
401245: 48 89 c7 mov %rax,%rdi
401248: e8 29 ff ff ff call 401176 <main::{lambda()#1}::operator()() const>
}();
# 看这里和上面的lea加载地址,0xb和0xa,只差1,很奇怪
40124d: 48 8d 45 f6 lea -0xa(%rbp),%rax
401251: 48 89 c7 mov %rax,%rdi
401254: e8 49 ff ff ff call 4011a2 <main::{lambda()#2}::operator()() const>
}();
# 第三个 lambda,多传入了 立即数1 作为第二个参数
401259: 48 8d 45 f7 lea -0x9(%rbp),%rax
40125d: be 01 00 00 00 mov $0x1,%esi
401262: 48 89 c7 mov %rax,%rdi
401265: e8 68 ff ff ff call 4011d2 <main::{lambda(int)#3}::operator()(int) const>
# 第四个,出现差异了
int a = 1, b = 2;
40127e: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
401285: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%rbp)
# rdi 是 -0x10(%rbp) 地址,这个地址是(a和b共同空间)的地址,只传递了一个地址!
[a, b]() -> int {
40128c: 8b 45 fc mov -0x4(%rbp),%eax
40128f: 89 45 f0 mov %eax,-0x10(%rbp)
401292: 8b 45 f8 mov -0x8(%rbp),%eax
401295: 89 45 f4 mov %eax,-0xc(%rbp)
}();
401298: 48 8d 45 f0 lea -0x10(%rbp),%rax
40129c: 48 89 c7 mov %rax,%rdi
40129f: e8 6e ff ff ff call 401212 <main::{lambda()#4}::operator()() const>
总结:
1、写一个 lambda,就会生成一个对应的函数
2、lambda 的传参,和常规函数传参一致,调用时候传递参数
3、不带捕获的 lambda,第一个参数是一个 无意义的 地址值,类似于类的this
,但大小是1,无意义
4、带捕获的lambda,第一个参数是参数列表地址(所有参数排序作为整个空间,传递空间地址,就像传递结构体指针)