C++11 Lambda 表达式参数传递

275 阅读3分钟

按值传递

如下所示

printI 将捕获变量 i , 所以可以直接在函数体中使用变量 i

int i = 100;
/*
* 输出 i 的地址:
* 刚好是下面 rdi 寄存器中的值
*/
std::cout << "address of i is:" << &i << std::endl;
auto printI = [=]() -> int {
    int temp = i;
    temp = temp*2;
    std::cout << "printI:" << temp << std::endl;
    return i;
};

打印下 变量 i 的值:

/* 
* i 的值
*/
i
$1 = 100

/*
* i 的地址
*/
&i
$2 = (int *) 0x7ffffffee300

值变量的捕获

printI 反汇编代码如下所示:

Dump of assembler code for function <lambda()>::operator()(void) const:
=> 0x00000000080009ca <+0>:     push   %rbp
   0x00000000080009cb <+1>:     mov    %rsp,%rbp
   0x00000000080009ce <+4>:     sub    $0x20,%rsp
   0x00000000080009d2 <+8>:     mov    %rdi,-0x18(%rbp)
   0x00000000080009d6 <+12>:    mov    -0x18(%rbp),%rax
   0x00000000080009da <+16>:    mov    (%rax),%eax
   0x00000000080009dc <+18>:    mov    %eax,-0x4(%rbp)
   0x00000000080009df <+21>:    shll   -0x4(%rbp)
   0x00000000080009e2 <+24>:    lea    0x1ac(%rip),%rsi        # 0x8000b95
   0x00000000080009e9 <+31>:    lea    0x201630(%rip),%rdi        # 0x8202020 <_ZSt4cout@@GLIBCXX_3.4>    0x00000000080009f0 <+38>:    callq  0x8000860 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x00000000080009f5 <+43>:    mov    %rax,%rdx
   0x00000000080009f8 <+46>:    mov    -0x4(%rbp),%eax
   0x00000000080009fb <+49>:    mov    %eax,%esi
   0x00000000080009fd <+51>:    mov    %rdx,%rdi
   0x0000000008000a00 <+54>:    callq  0x80008a0 <_ZNSolsEi@plt>
   0x0000000008000a05 <+59>:    mov    %rax,%rdx
   0x0000000008000a08 <+62>:    mov    0x2015c1(%rip),%rax        # 0x8201fd0
   0x0000000008000a0f <+69>:    mov    %rax,%rsi
   0x0000000008000a12 <+72>:    mov    %rdx,%rdi
   0x0000000008000a15 <+75>:    callq  0x8000870 <_ZNSolsEPFRSoS_E@plt>
   0x0000000008000a1a <+80>:    mov    -0x18(%rbp),%rax
   0x0000000008000a1e <+84>:    mov    (%rax),%eax
   0x0000000008000a20 <+86>:    leaveq
   0x0000000008000a21 <+87>:    retq

变量 i 保存到本地堆栈中,如下所示:

  • (rdi%) 寄存器指向的是第一个参数的地址
  • 将第一个参数的地址 (rdi%) 保存到新开辟的 栈帧(Frame)
  • (rdi%) 地址中的 值(value) 保存到 eax% 寄存器中
    • 按值传递:(rdi%) 中的值就是 100 (详见下述分析)
 0x00000000080009d2 <+8>:     mov    %rdi,-0x18(%rbp)
 0x00000000080009d6 <+12>:    mov    -0x18(%rbp),%rax
 0x00000000080009da <+16>:    mov    (%rax),%eax

此时各个寄存器中的值,如下所示:

rax            0x7ffffffee304   140737488282372
rbx            0x0      0
rcx            0xb40    2880
rdx            0x0      0
rsi            0x7fffff05d8c0   140737471961280
rdi            0x7ffffffee304   140737488282372
rbp            0x7ffffffee310   0x7ffffffee310
rsp            0x7ffffffee2f8   0x7ffffffee2f8
r8             0x7fffff05d8c0   140737471961280
r9             0x7fffff7d0d80   140737479773568
r10            0x6      6
r11            0x7ffffecee7e0   140737468360672
r12            0x80008c0        134219968
r13            0x7ffffffee3f0   140737488282608
r14            0x0      0
r15            0x0      0
rip            0x80009ca        0x80009ca <<lambda()>::operator()(void) const>
eflags         0x213    [ CF AF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

可以看到 rdi 寄存器中的值和代码 (&i) 中值

rdi => 0x7ffffffee304
&i  => 0x7ffffffee300

查看内存中的值

0x7ffffffee300: 100     100     -2040769536     1449909546
0x7ffffffee310: 134220560       0       -20374633       32767
0x7ffffffee320: -112    -1      -72712  32767
0x7ffffffee330: -112    1       134220322       0

重点是 0x7ffffffee3000x7ffffffee304 两个地址中的值

可以看到都是 100,这就是 按值传递

0x7ffffffee300 :: 100
0x7ffffffee304 :: 100

按引用传递

如下所示

变量 i 会在 Lambda 表达式中,被修改成为 3

int i = 100;
std::cout << "address of i is:" << &i << std::endl;
auto printI = [&]() -> void {
    i = 3;
};
std::cout << "i is :"<<  i << std::endl;

引用变量捕获

如下所示:

  • %rdi 寄存器,指向的是 变量 i 的内存地址的地址 (指针的指针的含义)
  • 经过一次解引用,获取 变量 i 的地址,存储到 %rax 寄存器中;
  • 再将立即数,送入到 变量 i 的地址中 (再次解引用)
Dump of assembler code for function <lambda()>::operator()(void) const:
=> 0x00000000080009ca <+0>:     push   %rbp
   0x00000000080009cb <+1>:     mov    %rsp,%rbp
   0x00000000080009ce <+4>:     mov    %rdi,-0x8(%rbp)
   0x00000000080009d2 <+8>:     mov    -0x8(%rbp),%rax
   0x00000000080009d6 <+12>:    mov    (%rax),%rax
   0x00000000080009d9 <+15>:    movl   $0x3,(%rax)
   0x00000000080009df <+21>:    nop
   0x00000000080009e0 <+22>:    pop    %rbp
   0x00000000080009e1 <+23>:    retq
End of assembler dump.

此时各个寄存器中值为

rax            0x7ffffffee300   140737488282368
rbx            0x0      0id) const (__closure=0x7fffff182d7e <std::ostream::flush()+30>)
rcx            0xb40    2880onProjects/mytest/main.cpp:16
rdx            0x0      0 [&]() -> void {
rsi            0x7fffff05d8c0   140737471961280
rdi            0x7ffffffee300   140737488282368:operator()(void) const:
rbp            0x7ffffffee310   0x7ffffffee310
rsp            0x7ffffffee2e8   0x7ffffffee2e8bp
r8             0x7fffff05d8c0   1407374719612808(%rbp)
r9             0x7fffff7d0d80   140737479773568p),%rax
r10            0x6      62>:    mov    (%rax),%rax
r11            0x7ffffecee7e0   140737468360672ax)
r12            0x80008c0        134219968
r13            0x7ffffffee3f0   140737488282608
r14            0x0      03>:    retq
r15            0x0      0
rip            0x80009ca        0x80009ca <<lambda()>::operator()(void) const>
eflags         0x213    [ CF AF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

(rdi%) 和 指针输出地址关系如下所示

rdi => 0x7ffffffee300         // 变量 i 的地址  
       0x7ffffffee2ff  ---
       0x7ffffffee2fe   *     // 变量 i 的值  
       0x7ffffffee2fd   *
&i  => 0x7ffffffee2fc  --- 

内存中的数据如下所示:

0x7ffffffee2fc: 0x00000064      0xfffee2fc      0x00007fff      0xe6c3a300
0x7ffffffee30c: 0x45b53a39      0x08000b00      0x00000000      0xfec91b97
0x7ffffffee31c: 0x00007fff      0xffffff90      0xffffffff      0xfffee3f8
0x7ffffffee32c: 0x00007fff      0xffffff90      0x00000001      0x080009e2

重点是 0x7ffffffee2fc0x7ffffffee300 两个内存位置的数据,含义如下所示

0x7ffffffee2fc: 0x00000064    // 0x64 = 100 变量 i 的值
0x7ffffffee300: 0xfffee2fc    // 存储了地址 (0x7ffffffee2fc)

总结

  • 按值传递
    • (rdi%) 中保存的是 变量副本值(value)
  • 按引用传递
    • (rdi%) 中保存的是 变量的地址的地址(address of address)