4.2.2 表达式短路
表达式短路通过逻辑与运算和逻辑或运算使语句根据条件在执行时发生中断,从而不予执行后面的语句。如何利用表达式短路来实现语句中断呢?根据逻辑与和逻辑或运算的特性,如果是与运算,当运算符左边的语句块为假值时,则直接返回假值,不执行右边的语句;如果是或运算,当运算符左边的语句块为真值时,直接返回真值,不执行右边的语句块。
利用表达式短路可以实现用递归方式计算累加和。下面我们将进一步学习和理解表达式短路的构成,如代码清单4-12所示。
代码清单4-12 使用逻辑与完成表达式短路
//C++源码说明:递归函数,用于计算整数累加,nNumber为累加值
int Accumulation(int nNumber){
//当nNumber等于0时,逻辑与运算符左边的值为假,将不会执行右边语句
//形成表达式短路,从而找到递归出口
nNumber&&(nNumber+=Accumulation(nNumber-1));
return nNumber;
}
//C++源码与对应汇编代码讲解
int Accumulation(int nNumber){
;在Debug版下添加汇编代码略
//C++源码对比,使用&&运算形成一个可短路语句
nNumber&&(nNumber+=Accumulation(nNumber-1));
;这里为短路模式汇编代码,比较变量nNumber是否等于0
0040BAA8 cmp dword ptr[ebp+8],0
;通过JE跳转,检查ZF标记位等于1跳转
0040BAAC je Accumulation+35h(0040bac5)
;跳转失败,进入递归调用
0040BAAE mov eax, dword ptr[ebp+8]
;对变量nNumber减1后,结果作为参数压栈
0040BAB1 sub eax,1
0040BAB4 push eax
;继续调用自己,形成递归
0040BAB5 call@ILT+30(Accumulation)(00401023)
0040BABA add esp,4
0040BABD mov ecx, dword ptr[ebp+8]
0040BAC0 add ecx, eax
0040BAC2 mov dword ptr[ebp+8],ecx
//C++源码对比,返回变量nNumber
return nNumber;
0040BAC5 mov eax, dword ptr[ebp+8]
}
在代码清单4-12中,通过递归函数Accumulation完成了整数累加和计算。在递归函数的构成中,必须要有一个出口,本示例选择了逻辑运算“&&”来制造递归函数的出口。通过使用CMP指令来检查运算符左边的语句是否为假值,根据跳转指令JE来决定是否跳过程序流程。当变量nNumber为假时,JE成功跳转,跳过递归函数调用,程序流程将会执行到出口return处。
逻辑运算“||”虽然与逻辑运算“&&”有些不同,但它们的构成原理相同,只需稍作修改就可以解决这一类型的问题。修改代码清单4-12,将逻辑与运算修改为逻辑或运算来实现表达式短路,如代码清单4-13所示。
代码清单4-13 使用逻辑与运算完成表达式短路
//C++源码说明:递归函数,用于计算整数累加,nNumber为累加值
int Accumulation(int nNumber){
//当nNumber等于0时,逻辑或运算符左边的值为真,将不会执行右面语句
//形成表达式短路,从而找到递归出口
(nNumber==0)||(nNumber+=Accumulation(nNumber-1));
return nNumber;
}
//C++源码与对应汇编代码讲解
int Accumulation(int nNumber){
;在Debug版下添加汇编代码略
105:(nNumber==0)||(nNumber+=Accumulation(nNumber-1));
;使用逻辑或运算造成的表达式短路,生成的反汇编代码与使用逻辑与是一样的
00401618 cmp dword ptr[ebp+8],0
0040161C je Accumulation+35h(00401635)
0040161E mov eax, dword ptr[ebp+8]
00401621 sub eax,1
00401624 push eax
00401625 call@ILT+30(Accumulation)(00401023)
0040162A add esp,4
0040162D mov ecx, dword ptr[ebp+8]
00401630 add ecx, eax
00401632 mov dword ptr[ebp+8],ecx
106:return nNumber;
00401635 mov eax, dword ptr[ebp+8]
通过代码清单4-13与代码清单4-12的对比发现,VC++6.0会将两种短路表达式编译为相同的汇编代码。虽然使用的逻辑运算符不同,但在两种情况下,运算符左边的语句块都是在与0值作比较,而且判定的结果都是等于0时不执行运算符右边的语句块,因此就变成了相同的汇编代码。
转换成汇编代码后,通过比较后跳转来实现短路,这种结构实质上就是分支结构。在反汇编代码中是没有表达式短路的,我们能够看到的都是分支结构。分支结构的知识见5.3节。