在看C++ Primer的过程中,发现C++11标准中添加了lambda和类型推断系统。这篇文章介绍了很多lambda的实例。
为了弄清楚lambda的实现,特地做了一个小实验。这一次只看non-mutable lambda。测试的gcc版本为4.6.3(貌似4.5以前的gcc不支持lambda表达式)。
代码:
1 //test.cpp 2 #include <iostream> 3 template<typename Func> 4 void test(Func f){ 5 std::cout<<f(3)<<std::endl; 6 } 7 template<typename Func> 8 void test2(Func f){ 9 std::cout<<f(13)<<std::endl; 10 } 11 int main(){ 12 int a = 1; 13 int b = 2; 14 auto f = [a,b](int c){return a+b+c;}; 15 test(f); 16 a=2;b=3; 17 test2(f); 18 return 0; 19 }
编译方法
g++ -std=c++0x test.cpp -o test
运行结果
./test
6
16
在这个例子中,代码第14行创建了一个non-mutable lambda。这里使用了类型自动推断,f的类型编译器会自动计算出来。[]称为capture list,里面的a和b的值同main里面的a和b的值是一样的,而且可以发现non-mutable lambda创建之后再对main中的a、b赋值,不会影响到capture list中的a和b的值。此外capture list中a和b是只读的,试图写这两个值时,编译器会报错。这个lamda函数的返回值类型是int,这也可以由编译器自动推断出来。
现在来看看生成的汇编代码,来看看C++是如何实现non-mutable lambda的。
下面这一段是从main函数的汇编代码中截取出来的,对应了C++代码第12行到15行。各行的意义参见注释。
1 8048612: c7 44 24 18 01 00 00 movl $0x1,0x18(%esp) #给main中的a赋值 2 8048619: 00 3 804861a: c7 44 24 1c 02 00 00 movl $0x2,0x1c(%esp) #给main中的b赋值 4 8048621: 00 5 8048622: 8b 44 24 18 mov 0x18(%esp),%eax 6 8048626: 89 44 24 10 mov %eax,0x10(%esp) #将main中的a值copy到capture list中的a(记为c_a) 7 804862a: 8b 44 24 1c mov 0x1c(%esp),%eax 8 804862e: 89 44 24 14 mov %eax,0x14(%esp) #将main中的b值copy到capture list中的b (记为c_b) 9 8048632: 8b 44 24 10 mov 0x10(%esp),%eax 10 8048636: 8b 54 24 14 mov 0x14(%esp),%edx 11 804863a: 89 04 24 mov %eax,(%esp) #%eax中放着c_a的值 12 804863d: 89 54 24 04 mov %edx,0x4(%esp) #将capture list中a、b的值分别取出,放在栈上作为函数调用的参数
#(分别记为c_a_copy1, c_b_copy1) 13 8048641: e8 2b 00 00 00 call 8048671 <_Z4testIZ4mainEUliE_EvT_> #调用test(f)
下面这一段是从函数模版test的汇编代码中截取的一段,对应了test中的f(3)这个表达式。
1 8048677: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) #将3作为参数放在栈上 2 804867e: 00 3 804867f: 8d 45 08 lea 0x8(%ebp),%eax #0x8(%ebp)对应的地址就是c_a_copy1 4 8048682: 89 04 24 mov %eax,(%esp) #将这个地址(c_a_copy1的地址)放在栈上 5 8048685: e8 6a ff ff ff call 80485f4 <_ZZ4mainENKUliE_clEi> #调用lambda的代码
下面是lambda代码对应的汇编码
1 80485f7: 8b 45 08 mov 0x8(%ebp),%eax #将c_a_copy1的地址取出 2 80485fa: 8b 10 mov (%eax),%edx #%edx中存放c_a_copy1的值 3 80485fc: 8b 45 08 mov 0x8(%ebp),%eax 4 80485ff: 8b 40 04 mov 0x4(%eax),%eax #%eax中存放着c_b_copy1的值 5 8048602: 01 d0 add %edx,%eax 6 8048604: 03 45 0c add 0xc(%ebp),%eax #0xc(%ebp)中放着参数c的值,着两行对应a+b+c
从上面的代码来看,C++在编译lambda的时将lambda作为一个特殊的函数来处理。non-mutable lambda编译出来的代码与函数的代码类似,capture list中的值是存放在创建者(本例中是main函数)的栈上的。使用non-mutable lambda的时候,capture list中的值被拷贝出来放在调用栈上,在lambda函数中取出来使用。
下面再来看一下main中的另一段汇编代码,这一段代码对应着C++中的16和17行。
1 8048646: c7 44 24 18 02 00 00 movl $0x2,0x18(%esp) #给a赋值为2,没有影响c_a的值 2 804864d: 00 3 804864e: c7 44 24 1c 03 00 00 movl $0x3,0x1c(%esp) #给b赋值为3,没有影响到c_b的值 4 8048655: 00 5 8048656: 8b 44 24 10 mov 0x10(%esp),%eax 6 804865a: 8b 54 24 14 mov 0x14(%esp),%edx 7 804865e: 89 04 24 mov %eax,(%esp) 8 8048661: 89 54 24 04 mov %edx,0x4(%esp) #仍然是从c_a和c_b中拷贝值放在栈上 9 8048665: e8 42 00 00 00 call 80486ac <_Z5test2IZ4mainEUliE_EvT_> #调用test2(f),后面部分的原理与test(f)相似
从这段代码我们可以看出,capture list中的变量的值使用的是non-mutable lambda创建的时刻的值,以后不随着capture list外的值的变化而变化。
这篇文章分析了一下mutable lambda的实现。