C++中lambda的实现(2)

之前写了一篇C++中的lambda的实现(1),从汇编语言的角度来分析了一下non-mutable lambda的实现方式。这篇文章主要介绍一下mutable lambda的实现方式。而这篇文章中有比较详细的lambda语法示例

实验用gcc版本4.7.2,据说4.5以前的gcc不能支持C++11中的lambda。

C++源码

 1 //lambda.cpp
 2 #include <iostream>
 3 using namespace std;
 4 int main(){
 5     int a,b;
 6     a = 1;
 7     b = 2;
 8     auto f = [&a, &b](int c)->int{ a++; b++; return a+b+c;}; //创建一个lambda表达式,'&'表示a, b以引用传递,
//可以被修改,并且修改会反映到main中的a、b
9 cout<<"result= "<<f(3)<<endl; 10 cout<<"a= "<<a<<endl; 11 cout<<"b= "<<b<<endl; 12 return 0; 13 }

编译运行

g++ -std=c++0x lambda.cpp -o lambda
./lambda
result= 8
a= 2
b= 3

从结果来看,lamba表达式执行之后,a、b的值成功地被修改了。

下面这一段是main函数6-8行以及f(3)对应的汇编代码,同上次分析的一样,lambda在C++中编译成一个函数。

 1  80487ea:       c7 44 24 10 01 00 00    movl   $0x1,0x10(%esp)  # a=1
 2  80487f1:       00 
 3  80487f2:       c7 44 24 14 02 00 00    movl   $0x2,0x14(%esp)  # b=2
 4  80487f9:       00 
 5  80487fa:       8d 44 24 10             lea    0x10(%esp),%eax
 6  80487fe:       89 44 24 18             mov    %eax,0x18(%esp)  #0x18(%ebp)(记为ref_a)中存放了a的地址
 7  8048802:       8d 44 24 14             lea    0x14(%esp),%eax
 8  8048806:       89 44 24 1c             mov    %eax,0x1c(%esp)  #0x1c(%ebp)(记为ref_b)中存放了b的地址
 9  804880a:       c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)   #将f(3)中的参数3放在栈上
10  8048811:       00 
11  8048812:       8d 44 24 18             lea    0x18(%esp),%eax
12  8048816:       89 04 24                mov    %eax,(%esp)      #将ref_a的地址放在栈上
13  8048819:       e8 8e ff ff ff          call   80487ac <_ZZ4mainENKUliE_clEi>  #调用lambda对应的函数

下面这一段是lambda函数所对应的汇编代码

 80487ac:       55                      push   %ebp
 80487ad:       89 e5                   mov    %esp,%ebp
 80487af:       8b 45 08                mov    0x8(%ebp),%eax         #取出上面一段汇编代码中12行所存的ref_a的地址
 80487b2:       8b 00                   mov    (%eax),%eax            #取出ref_a的值,也就是a的地址
 80487b4:       8b 10                   mov    (%eax),%edx            #取出a的值到edx
 80487b6:       83 c2 01                add    $0x1,%edx              # a++
 80487b9:       89 10                   mov    %edx,(%eax)            #存回a,注意这个时候是存回到了main函数栈上的a里面
 80487bb:       8b 45 08                mov    0x8(%ebp),%eax         #取出ref_a的地址
 80487be:       8b 40 04                mov    0x4(%eax),%eax         #ref_a和ref_b是紧挨着存储的
#这一条指令取出ref_b的值,也就是b的地址 8
0487c1: 8b 10 mov (%eax),%edx #取出b的值,放入edx 80487c3: 83 c2 01 add $0x1,%edx # b++ 80487c6: 89 10 mov %edx,(%eax) #存回b 80487c8: 8b 45 08 mov 0x8(%ebp),%eax 80487cb: 8b 00 mov (%eax),%eax 80487cd: 8b 10 mov (%eax),%edx #再取a的值,放在edx 80487cf: 8b 45 08 mov 0x8(%ebp),%eax 80487d2: 8b 40 04 mov 0x4(%eax),%eax 80487d5: 8b 00 mov (%eax),%eax 80487d7: 01 c2 add %eax,%edx #edx = a+b 80487d9: 8b 45 0c mov 0xc(%ebp),%eax #取出上一段汇编中第9行放在站上的参数3 80487dc: 01 d0 add %edx,%eax #eax= a+b+c,而函数返回值放在eax中 80487de: 5d pop %ebp 80487df: c3 ret

从上面的汇编分析来看,gcc在实现mutable lambda的时候,将lambda的代码编译为函数,将capture list中的可写变量的地址通过一些手段传入到lambda实现函数中。对比之下,non-mutable的lambda则将capture list中变量的值复制一遍,再将复制出来的变量的地址通过一些手段传入lambda实现函数。