算法结果溢出:
一个无符号数产生溢出后会从一个最大数变为最小数
有符号数溢出会修改符号位
#include
using namespace std;
int main()
{
for(int i = 1; i > 0 ; i += 1)
{
printf("%d \r\n",i);
}
return 0;
}
上面代码看上去像一个死循环,其实不然,主要原因是i为有符号整数,i的最大值运行为0x7FFFFFFF,在加1时最终会变成0x8000000,这是最高位为1,则为负数,因此跳出
溢出是由于数据进位后超出数据的保存范围导致的
进位:无符号数超出存储范围叫做进位。因为没有符号位,不会破坏数据,而多出的1位数据会被进位标志位CF保存,数据产生了进位,只是进位后保存在CF中。可以通过CF查看,同时也可判断有没有进位
溢出:有符号数超出存储范围叫做溢出,由于数据进位,从而破坏了有符号数的最高位-符号位。溢出只针对有符号数。可查看OF,检查是否溢出。OF判断是这样的:如果参与计算的数值符号一致,而计算结果符号不同,则判断OF成立。
也有其他操作指令会导致溢出或进位
自增和自减:
大家都熟悉自增和自减运算,也都知道规律:前置和后置,也就是先自增后运算和先运算后自增
下面用代码看看
源代码:
#include
using namespace std;
int main(int argc,char *argv[])
{
int nVarOne = argc;
int nVarTwo = argc;
nVarTwo = 5 + (nVarOne ++);
nVarTwo = 5 + (++ nVarOne);
nVarOne = 5 + (nVarTwo --);
nVarOne = 5 + (-- nVarTwo);
return 0;
}
汇编代码:
1: #include
2:
3: using namespace std;
4:
5: int main(int argc,char *argv[])
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,48h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-48h]
0040125C mov ecx,12h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: int nVarOne = argc;
00401268 mov eax,dword ptr [ebp+8]
0040126B mov dword ptr [ebp-4],eax
8: int nVarTwo = argc;
0040126E mov ecx,dword ptr [ebp+8]
00401271 mov dword ptr [ebp-8],ecx
9:
10: nVarTwo = 5 + (nVarOne ++);
00401274 mov edx,dword ptr [ebp-4]
00401277 add edx,5
0040127A mov dword ptr [ebp-8],edx
0040127D mov eax,dword ptr [ebp-4]
00401280 add eax,1
00401283 mov dword ptr [ebp-4],eax
11: nVarTwo = 5 + (++ nVarOne);
00401286 mov ecx,dword ptr [ebp-4]
00401289 add ecx,1
0040128C mov dword ptr [ebp-4],ecx
0040128F mov edx,dword ptr [ebp-4]
00401292 add edx,5
00401295 mov dword ptr [ebp-8],edx
12:
13: nVarOne = 5 + (nVarTwo --);
00401298 mov eax,dword ptr [ebp-8]
0040129B add eax,5
0040129E mov dword ptr [ebp-4],eax
004012A1 mov ecx,dword ptr [ebp-8]
004012A4 sub ecx,1
004012A7 mov dword ptr [ebp-8],ecx
14: nVarOne = 5 + (-- nVarTwo);
004012AA mov edx,dword ptr [ebp-8]
004012AD sub edx,1
004012B0 mov dword ptr [ebp-8],edx
004012B3 mov eax,dword ptr [ebp-8]
004012B6 add eax,5
004012B9 mov dword ptr [ebp-4],eax
15:
16: return 0;
004012BC xor eax,eax
17: }
004012BE pop edi
004012BF pop esi
004012C0 pop ebx
004012C1 mov esp,ebp
004012C3 pop ebp
004012C4 ret
汇编级代码实现我们很清晰的看出自增自减的实现
关系运算和逻辑运算:
可以利用各种类型的跳转来实现两者间的关系比较,根据比较结果所影响到的标志位来选择对应的条件跳转指令
跳转指令大家都熟悉,不在啰嗦
表达式短路&&:
源代码:
#include
using namespace std;
int Accumulation(int nNumber)
{
nNumber && (nNumber += Accumulation(nNumber - 1));
return nNumber;
}
int main(int argc,char *argv[])
{
Accumulation(12);
return 0;
}
汇编代码分析:
1: #include
2:
3: using namespace std;
4:
5: int Accumulation(int nNumber)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: nNumber && (nNumber += Accumulation(nNumber - 1));
00401268 cmp dword ptr [ebp+8],0
0040126C je Accumulation+35h (00401285)
0040126E mov eax,dword ptr [ebp+8]
00401271 sub eax,1
00401274 push eax
00401275 call @ILT+10(Accumulation) (0040100f)
0040127A add esp,4
0040127D mov ecx,dword ptr [ebp+8]
00401280 add ecx,eax
00401282 mov dword ptr [ebp+8],ecx
8: return nNumber;
00401285 mov eax,dword ptr [ebp+8]
9: }
00401288 pop edi
00401289 pop esi
0040128A pop ebx
0040128B add esp,40h
0040128E cmp ebp,esp
00401290 call __chkesp (004081b0)
00401295 mov esp,ebp
00401297 pop ebp
00401298 ret
10:
11: int main(int argc,char *argv[])
12: {
004012B0 push ebp
004012B1 mov ebp,esp
004012B3 sub esp,40h
004012B6 push ebx
004012B7 push esi
004012B8 push edi
004012B9 lea edi,[ebp-40h]
004012BC mov ecx,10h
004012C1 mov eax,0CCCCCCCCh
004012C6 rep stos dword ptr [edi]
13: Accumulation(12);
004012C8 push 0Ch
004012CA call @ILT+10(Accumulation) (0040100f)
004012CF add esp,4
14:
15: return 0;
004012D2 xor eax,eax
16: }
004012D4 pop edi
004012D5 pop esi
004012D6 pop ebx
004012D7 add esp,40h
004012DA cmp ebp,esp
004012DC call __chkesp (004081b0)
004012E1 mov esp,ebp
004012E3 pop ebp
004012E4 ret
我们主要看短路情况:从第一段代码分析可以看出 && 前面的表达式是递归跳出的条件
这正是利用了短路现象,使得代码精炼
我们在来看看||运算符的短路情况:
源代码基本一样,直接看汇编对照代码:
1: #include
2:
3: using namespace std;
4:
5: int Accumulation(int nNumber)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: (nNumber==0) || (nNumber += Accumulation(nNumber - 1));
00401268 cmp dword ptr [ebp+8],0
0040126C je Accumulation+35h (00401285)
0040126E mov eax,dword ptr [ebp+8]
00401271 sub eax,1
00401274 push eax
00401275 call @ILT+10(Accumulation) (0040100f)
0040127A add esp,4
0040127D mov ecx,dword ptr [ebp+8]
00401280 add ecx,eax
00401282 mov dword ptr [ebp+8],ecx
8: return nNumber;
00401285 mov eax,dword ptr [ebp+8]
9: }
00401288 pop edi
00401289 pop esi
0040128A pop ebx
0040128B add esp,40h
0040128E cmp ebp,esp
00401290 call __chkesp (004081b0)
00401295 mov esp,ebp
00401297 pop ebp
00401298 ret
10:
11: int main(int argc,char *argv[])
12: {
004012B0 push ebp
004012B1 mov ebp,esp
004012B3 sub esp,40h
004012B6 push ebx
004012B7 push esi
004012B8 push edi
004012B9 lea edi,[ebp-40h]
004012BC mov ecx,10h
004012C1 mov eax,0CCCCCCCCh
004012C6 rep stos dword ptr [edi]
13: Accumulation(12);
004012C8 push 0Ch
004012CA call @ILT+10(Accumulation) (0040100f)
004012CF add esp,4
14:
15: return 0;
004012D2 xor eax,eax
16: }
004012D4 pop edi
004012D5 pop esi
004012D6 pop ebx
004012D7 add esp,40h
004012DA cmp ebp,esp
004012DC call __chkesp (004081b0)
004012E1 mov esp,ebp
004012E3 pop ebp
004012E4 ret
对比我们发现,虽然我们使用了不同的逻辑运算符,但是生成了相同的汇编代码,说明它们的功能完全一样
条件表达式:
三目表达式我们都不陌生,C里面仅有的一个
表达式1 ? 表达式2 : 表达式3
条件表达式有4种转换方案:
>方案1 : 表达式1为简单比较,而表达式2和表达式3两者的差值等于1
>方案2 : 表达式1为简单比较,而表达式2和表达式3两者的差值大于1
>方案3 : 表达式1为复杂比较,而表达式2和表达式3两者的差值大于1
>方案4 : 表达式2和表达式3有一个为变量,于是无优化
例子:
方案1:
源代码:
int Condition(int argc,int n)
{
return argc == 5 ? 5 : 6;
}
汇编代码:
00401678 xor eax,eax
0040167A cmp dword ptr [ebp+8],5
0040167E setne al //进行平衡 上面不等于0,设置al,否则为0
00401681 add eax,5
但是 setne也只能用在相差为1 的平衡上面,如果大于1,它就无能无力了
方案2:
源代码:
int Condition(int argc,int n)
{
return argc == 5 ? 4 : 10;
}
汇编代码:
00401678 mov eax,dword ptr [ebp+8]
0040167B sub eax,5
0040167E neg eax
00401680 sbb eax,eax
00401682 and eax,6
00401685 add eax,4
对于arg==5这种等值比较,VC++会使用减法和求补运算来判断其是否为真值
只要argc不等于5,在执行sub指令后,eax的值就不为0;
然后neg指令,eax的符号位就会发生变化,CF置1.
接下来执行借位减法eax = eax - eax - CF。当CF为1时,eax中的值将为0xFFFFFFFF,否则为0
使用eax与6做位与运算后,如果eax中数值原来为-1,则解果为6,加4等于数值10
否则argc == 5 , eax始终为0,加4为数值4
但是,当表达式1为一个区间是,这样就不行了
方案3:
源代码:
int Condition(int argc,int n)
{
return argc <= 8 ? 4 : 10;
}
汇编代码分析:
00401678 xor eax,eax //清零eax
0040167A cmp dword ptr [ebp+8],8 //比较argc和8
0040167E setg al //如果argc>8 al=1,否则为0
00401681 dec eax //eax减去1,eax现在=0xFFFFFFFF或0
00401682 and al,0FAh //eax现在为0xFFFFFFFA(-6补码)或0
00401685 add eax,0Ah //eax现在为4或10
上面已经解释的很清楚。
但是当表达式2或表达式3中的值为未知数时,就无法使用之前的方法去优化了
编译器会按照常规根据语句流程进行比较和判断
方案4:
源代码:
int Condition(int argc,int n)
{
return argc ? 4 : n;
}
汇编语言分析:
00401448 cmp dword ptr [ebp+8],0
0040144C je Condition+27h (00401457)
0040144E mov dword ptr [ebp-4],8
00401455 jmp Condition+2Dh (0040145d)
00401457 mov eax,dword ptr [ebp+0Ch]
0040145A mov dword ptr [ebp-4],eax
0040145D mov eax,dword ptr [ebp-4]
00401466 ret
位运算
<< 左移运算,最高位左移到CF中,最低位补零
>> 右移运算,最高位不变,最低位右移到CF中
看代码是如何实现的:
源代码:
#include
using namespace std;
int BitOperation(int argc)
{
argc = argc << 3;
argc = argc >> 5;
argc = argc | 0xFFFF0000;
argc = argc & 0x0000FFFF;
argc = argc ^ 0xFFFF0000;
argc = ~argc;
return argc;
}
int main()
{
int a = 5;
BitOperation(a);
return 0;
}
汇编代码分析:
1: #include
2:
3: using namespace std;
4:
5: int BitOperation(int argc)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: argc = argc << 3;
00401268 mov eax,dword ptr [ebp+8]
0040126B shl eax,3
0040126E mov dword ptr [ebp+8],eax
8: argc = argc >> 5;
00401271 mov ecx,dword ptr [ebp+8]
00401274 sar ecx,5
00401277 mov dword ptr [ebp+8],ecx
9: argc = argc | 0xFFFF0000;
0040127A mov edx,dword ptr [ebp+8]
0040127D or edx,0FFFF0000h
00401283 mov dword ptr [ebp+8],edx
10: argc = argc & 0x0000FFFF;
00401286 mov eax,dword ptr [ebp+8]
00401289 and eax,0FFFFh
0040128E mov dword ptr [ebp+8],eax
11: argc = argc ^ 0xFFFF0000;
00401291 mov ecx,dword ptr [ebp+8]
00401294 xor ecx,0FFFF0000h
0040129A mov dword ptr [ebp+8],ecx
12: argc = ~argc;
0040129D mov edx,dword ptr [ebp+8]
004012A0 not edx
004012A2 mov dword ptr [ebp+8],edx
13:
14: return argc;
004012A5 mov eax,dword ptr [ebp+8]
15: }
004012A8 pop edi
004012A9 pop esi
004012AA pop ebx
004012AB mov esp,ebp
004012AD pop ebp
004012AE ret
16:
17: int main()
18: {
004012D0 push ebp
004012D1 mov ebp,esp
004012D3 sub esp,44h
004012D6 push ebx
004012D7 push esi
004012D8 push edi
004012D9 lea edi,[ebp-44h]
004012DC mov ecx,11h
004012E1 mov eax,0CCCCCCCCh
004012E6 rep stos dword ptr [edi]
19: int a = 5;
004012E8 mov dword ptr [ebp-4],5
20: BitOperation(a);
004012EF mov eax,dword ptr [ebp-4]
004012F2 push eax
004012F3 call @ILT+10(BitOperation) (0040100f)
004012F8 add esp,4
21:
22: return 0;
004012FB xor eax,eax
23: }
004012FD pop edi
004012FE pop esi
004012FF pop ebx
00401300 add esp,44h
00401303 cmp ebp,esp
00401305 call __chkesp (004081d0)
0040130A mov esp,ebp
0040130C pop ebp
0040130D ret
上面的反汇编代码比较简单,是有符号的位运算
下面无符号的还有点不一样:
源代码:
#include
using namespace std;
void BitOperation(int argc)
{
unsigned int nVar = argc;
nVar <<= 3;
nVar >>= 5;
}
int main()
{
int a = 5;
BitOperation(a);
return 0;
}
汇编代码分析:
1: #include
2:
3: using namespace std;
4:
5: void BitOperation(int argc)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,44h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-44h]
0040125C mov ecx,11h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: unsigned int nVar = argc;
00401268 mov eax,dword ptr [ebp+8]
0040126B mov dword ptr [ebp-4],eax
8: nVar <<= 3;
0040126E mov ecx,dword ptr [ebp-4]
00401271 shl ecx,3
00401274 mov dword ptr [ebp-4],ecx
9: nVar >>= 5;
00401277 mov edx,dword ptr [ebp-4]
0040127A shr edx,5
0040127D mov dword ptr [ebp-4],edx
10:
11: }
00401280 pop edi
00401281 pop esi
00401282 pop ebx
00401283 mov esp,ebp
00401285 pop ebp
00401286 ret
12:
13: int main()
14: {
004012A0 push ebp
004012A1 mov ebp,esp
004012A3 sub esp,44h
004012A6 push ebx
004012A7 push esi
004012A8 push edi
004012A9 lea edi,[ebp-44h]
004012AC mov ecx,11h
004012B1 mov eax,0CCCCCCCCh
004012B6 rep stos dword ptr [edi]
15: int a = 5;
004012B8 mov dword ptr [ebp-4],5
16: BitOperation(a);
004012BF mov eax,dword ptr [ebp-4]
004012C2 push eax
004012C3 call @ILT+10(BitOperation) (0040100f)
004012C8 add esp,4
17:
18: return 0;
004012CB xor eax,eax
19: }
004012CD pop edi
004012CE pop esi
004012CF pop ebx
004012D0 add esp,44h
004012D3 cmp ebp,esp
004012D5 call __chkesp (004081a0)
004012DA mov esp,ebp
004012DC pop ebp
004012DD ret