C++中lambda的实现(1)

在看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的实现