最近开始看C++11的新语法,觉得有些知识还是要更进一步认识下,下面仅就这段简短的代码来聊聊Lamda,为啥在这段代码里面 lamda中的x 是 10 而不是 15呢?
int testFun() {
int x = 10;
int y = 20;
int ret = 0;
auto f = [x, &y] ()-> int {
y++;
return x+y;
};
x = 15;
y = 100;
ret = f();
return ret;
}
上面这段代码,返回的ret = 111, 而不是 116,这个是为啥呢?
以下是它的ARM 汇编代码(没办法,其它架构不熟悉,只能拿ARM汇编来分析 :- ))
.text:00008AD4 ; =============== S U B R O U T I N E =======================================
.text:00008AD4
.text:00008AD4
.text:00008AD4 ; _DWORD testFun(void)
.text:00008AD4 EXPORT _Z7testFunv
.text:00008AD4 _Z7testFunv ; CODE XREF: testFun(void)+8j
.text:00008AD4 ; DATA XREF: .got:_Z7testFunv_ptro
.text:00008AD4
.text:00008AD4 var_28 = -0x28
.text:00008AD4 var_24 = -0x24
.text:00008AD4 var_20 = -0x20
.text:00008AD4 var_1C = -0x1C
.text:00008AD4 var_18 = -0x18
.text:00008AD4 var_14 = -0x14
.text:00008AD4 var_10 = -0x10
.text:00008AD4 var_C = -0xC
.text:00008AD4
.text:00008AD4 PUSH {R7,LR}
.text:00008AD6 MOV R7, SP
.text:00008AD8 SUB SP, SP, #0x20
.text:00008ADA LDR R0, =(__stack_chk_guard_ptr - 0x8AE0)
.text:00008ADC ADD R0, PC ; __stack_chk_guard_ptr
.text:00008ADE LDR R0, [R0] ; __stack_chk_guard
.text:00008AE0 LDR R1, [R0]
.text:00008AE2 STR R1, [SP,#0x28+var_C]
.text:00008AE4 MOVS R1, #0xA
.text:00008AE6 STR R1, [SP,#0x28+var_1C]
.text:00008AE8 MOVS R1, #0x14
.text:00008AEA STR R1, [SP,#0x28+var_10]
.text:00008AEC MOVS R1, #0
.text:00008AEE STR R1, [SP,#0x28+var_20]
.text:00008AF0 LDR R1, [SP,#0x28+var_1C]
.text:00008AF2 STR R1, [SP,#0x28+var_18]
.text:00008AF4 ADD R1, SP, #0x28+var_10
.text:00008AF6 STR R1, [SP,#0x28+var_14]
.text:00008AF8 MOVS R1, #0xF
.text:00008AFA STR R1, [SP,#0x28+var_1C]
.text:00008AFC MOVS R1, #0x64
.text:00008AFE STR R1, [SP,#0x28+var_10]
.text:00008B00 ADD R1, SP, #0x28+var_18
.text:00008B02 STR R0, [SP,#0x28+var_24]
.text:00008B04 MOV R0, R1
.text:00008B06 BL sub_8B2C
.text:00008B0A STR R0, [SP,#0x28+var_20]
.text:00008B0C LDR R0, [SP,#0x28+var_20]
.text:00008B0E LDR R1, [SP,#0x28+var_24]
.text:00008B10 LDR.W LR, [R1]
.text:00008B14 LDR R2, [SP,#0x28+var_C]
.text:00008B16 CMP LR, R2
.text:00008B18 STR R0, [SP,#0x28+var_28]
.text:00008B1A BNE loc_8B24
.text:00008B1C B loc_8B1E
.text:00008B1E ; ---------------------------------------------------------------------------
.text:00008B1E
.text:00008B1E loc_8B1E ; CODE XREF: testFun(void)+48j
.text:00008B1E LDR R0, [SP,#0x28+var_28]
.text:00008B20 ADD SP, SP, #0x20
.text:00008B22 POP {R7,PC}
.text:00008B24 ; ---------------------------------------------------------------------------
.text:00008B24
.text:00008B24 loc_8B24 ; CODE XREF: testFun(void)+46j
.text:00008B24 BLX __stack_chk_fail
.text:00008B24 ; End of function testFun(void)
.text:00008B24
.text:00008B24 ; ---------------------------------------------------------------------------
.text:00008B28 off_8B28 DCD __stack_chk_guard_ptr - 0x8AE0
.text:00008B28 ; DATA XREF: testFun(void)+6r
.text:00008B2C
.text:00008B2C ; =============== S U B R O U T I N E =======================================
.text:00008B2C
.text:00008B2C
.text:00008B2C sub_8B2C ; CODE XREF: testFun(void)+32p
.text:00008B2C
.text:00008B2C var_8 = -8
.text:00008B2C var_4 = -4
.text:00008B2C
.text:00008B2C SUB SP, SP, #8
.text:00008B2E MOV R1, R0
.text:00008B30 STR R0, [SP,#8+var_4]
.text:00008B32 LDR R0, [SP,#8+var_4]
.text:00008B34 LDR R2, [R0,#4]
.text:00008B36 LDR R3, [R2]
.text:00008B38 ADDS R3, #1
.text:00008B3A STR R3, [R2]
.text:00008B3C LDR R2, [R0]
.text:00008B3E LDR R0, [R0,#4]
.text:00008B40 LDR R0, [R0]
.text:00008B42 ADD R0, R2
.text:00008B44 STR R1, [SP,#8+var_8]
.text:00008B46 ADD SP, SP, #8
.text:00008B48 BX LR
.text:00008B48 ; End of function sub_8B2C
.text:00008B48
.text:00008B48 ; ---------------------------------------------------------------------------
下面我们分析整个过程,堆栈的变化如下:
高地址 | ||||
0x20 | ||||
0x1c | ||||
0x18 | 0x14 | 0x64 | ||
0x14 | y的地址(SP+0x18) | |||
0x10 | x内容的拷贝(0xA) | R0 | ||
0xc | 0xA | 0xF | ||
0x8 | 0x0 | 结果写到这个地址 | ||
0x4 | R0 | |||
SP | ||||
原SP+0x10地址 | ||||
SP | R2是原SP+0x14地址 R3是上述地址的内容 |
执行Lamda表达式实际跟调用函数基本也没啥区别,编译的时候还是给编译成单独的函数调用过程,如下是用BL调用过程 在执行.text:00008AFE STR R1, [SP,#0x28+var_10] 之后,实际堆栈入红色部分第2列所示,我们看到实际上对lamda表达式中的[x, &y]实际就是 SP+0x10 和SP+0x14 地址的内容,它们就是我们赋值的 0xA 和 原SP+0x18。
在这里需要特别注意下:
.text:00008AF0 LDR R1, [SP,#0x28+var_1C]
.text:00008AF2 STR R1, [SP,#0x28+var_18]
.text:00008AF4 ADD R1, SP, #0x28+var_10
.text:00008AF6 STR R1, [SP,#0x28+var_14]
在这里,前面两句是操作地址对应的内容,也就是x拷贝了一份到SP+0x10地址,后面这个实际是将y的地址写入到SP+0x14的地址。之后R0指向地址SP+0x10,并且我们调用了子函数,也就是lamda函数,
.text:00008B06 BL sub_8B2C
在具体的函数内部,也是会去压栈和出栈。对应的是黄色的部分。我们看下函数的R0,实际是指向原先的SP+0x10,对应的内容是x变量的拷贝,它的下一个单元SP+0x14实际就是y变量的地址。关于sub_8B2C内容,解释也就是很简单了。
总结以下,对于lamda来说,中括号里面的只要不带 &, 那么就表示这个变量是原变量的一个拷贝,外部变量的变化,不会影响lamda里面的变化,lamda只关心定义这个的时候的具体数值。而带上&,则表示这个变量是原变量的别名,原变量发生变化,会影响这个变量,同理这个变量发生变化,也会引发原变量的变化。