所谓的流程控制语句在C/C++中就是,if-else switch for while语句
也就是条件语句和循环语句的合集
if-else
纯if语句
if在c语言中只能判断真或假,真就执行if里面的东西,为假就跳过
void iftest(int a)
{
00413270 push ebp
00413271 mov ebp,esp
00413273 sub esp,40h
00413276 push ebx
00413277 push esi
00413278 push edi
if (a > 0)
00413279 cmp dword ptr [a],0
0041327D jle iftest+1Eh (041328Eh)
{
printf("a>0 \n");
0041327F push offset string "a>0\n" (0415B64h)
00413284 call _printf (04110AFh)
00413289 add esp,4
return;
0041328C jmp iftest+2Bh (041329Bh)
}
printf("if语句调用失败\n");
0041328E push offset string "if\xd3\xef\xbe\xe4\xb5\xf7\xd3\xc3\xca\xa7\xb0\xdc\n" (0415C7Ch)
00413293 call _printf (04110AFh)
00413298 add esp,4
}
0041329B pop edi
0041329C pop esi
0041329D pop ebx
0041329E mov esp,ebp
004132A0 pop ebp
004132A1 ret
可以很清楚的看到,对于if语句直接一个cmp比较,然后进行反着跳转,如果你要大于等于那么要跳转的jcc语句就是小于,跳转就是跳过if语句,如果跳转失败,就顺着执行下去。
纯if和else语句
纯if和else语句的意思,就是只有if 和else 没有else if这样的语句
void SingleIfElsetest(int a)
{
004116C0 push ebp
004116C1 mov ebp,esp
004116C3 sub esp,40h
004116C6 push ebx
004116C7 push esi
004116C8 push edi
if (a > 0)
004116C9 cmp dword ptr [a],0
004116CD jle SingleIfElsetest+1Eh (04116DEh)
{
printf("a>0\n");
004116CF push offset string "a>0\n" (0415C8Ch)
004116D4 call _printf (04110AFh)
004116D9 add esp,4
}
004116DC jmp ___local_stdio_printf_options+0Bh (04116EBh)
else
{
printf("a<=0\n");
004116DE push offset string "a<=0\n" (0415C94h)
004116E3 call _printf (04110AFh)
004116E8 add esp,4
}
}
004116EB pop edi
004116EC pop esi
004116ED pop ebx
004116EE mov esp,ebp
004116F0 pop ebp
004116F1 ret
纯if else语句就是第一个条件满足进入后,然后跳转到else语句后的下一条指令
在if的语句中添加一个jmp
然后在if中添加一个相反的jcc指令来看是否跳转到else语句中
if和else还有else if混合
void BlendIfElsetest(int a)
{
004116C0 push ebp
004116C1 mov ebp,esp
004116C3 sub esp,40h
004116C6 push ebx
004116C7 push esi
004116C8 push edi
if (a > 0)
004116C9 cmp dword ptr [a],0
004116CD jle BlendIfElsetest+1Eh (04116DEh)
{
printf("a>0\n");
004116CF push offset string "a>0\n" (0415C8Ch)
004116D4 call _printf (04110AFh)
004116D9 add esp,4
}
004116DC jmp ___local_stdio_printf_options (0411700h)
else if (a == 0)
004116DE cmp dword ptr [a],0
004116E2 jne ___local_stdio_printf_options+13h (04116F3h)
{
printf("a==0\n");
004116E4 push offset string "a==0\n" (0415C9Ch)
004116E9 call _printf (04110AFh)
004116EE add esp,4
}
004116F1 jmp ___local_stdio_printf_options (0411700h)
else
{
printf("a<0\n");
004116F3 push offset string "a<0\n" (0415CA4h)
004116F8 call _printf (04110AFh)
004116FD add esp,4
}
}
00411700 pop edi
00411701 pop esi
00411702 pop ebx
00411703 mov esp,ebp
00411705 pop ebp
00411706 ret
先进行第一个if语句的反向判断,如果jcc跳转成功就跳转到else if语句进行判断,如果else if反向判断成功就跳转到else执行else的语句,如果第一个if语句反向判断失败,就在执行完后jmp到else结束的下一条语句,如果第二个反向判断失败,就在执行完第二条指令后jmp到else结束的下一条指令
switch语句
简单的switch语句-只有三个case
void EasySwitchTest(int a)
{
00411710 push ebp
00411711 mov ebp,esp
00411713 sub esp,44h
00411716 push ebx
00411717 push esi
00411718 push edi
switch (a)
00411719 mov eax,dword ptr [a]
0041171C mov dword ptr [ebp-44h],eax
0041171F cmp dword ptr [ebp-44h],1
00411723 je EasySwitchTest+23h (0411733h)
00411725 cmp dword ptr [ebp-44h],2
00411729 je EasySwitchTest+32h (0411742h)
0041172B cmp dword ptr [ebp-44h],3
0041172F je ___local_stdio_printf_options+1h (0411751h)
00411731 jmp ___local_stdio_printf_options+0Eh (041175Eh)
{
case 1:
printf("结果为1\n");
00411733 push offset string "\xbd\xe1\xb9\xfb\xce\xaa1\n" (0415B34h)
00411738 call _printf (04110AFh)
0041173D add esp,4
break;
00411740 jmp ___local_stdio_printf_options+0Eh (041175Eh)
case 2:
printf("结果为2\n");
00411742 push offset string "\xbd\xe1\xb9\xfb\xce\xaa2\n" (0415B40h)
00411747 call _printf (04110AFh)
0041174C add esp,4
break;
0041174F jmp ___local_stdio_printf_options+0Eh (041175Eh)
case 3:
printf("结果为3\n");
00411751 push offset string "\xbd\xe1\xb9\xfb\xce\xaa3\n" (0415B4Ch)
00411756 call _printf (04110AFh)
0041175B add esp,4
break;
default:
break;
}
}
0041175E pop edi
0041175F pop esi
00411760 pop ebx
00411761 mov esp,ebp
00411763 pop ebp
00411764 ret
可以看到switch语句和if-else的区别,switch语句是直接拿来比较值,然后跳转到各个case的结果,而if-esle语句是一个一个比较跳转,switch语句更加简洁明了把各个case重新在比较跳转的下面来写
复杂的switch语句-大于3个case
void EasySwitchTest(int a)
{
00413BA0 push ebp
00413BA1 mov ebp,esp
00413BA3 sub esp,44h
00413BA6 push ebx
00413BA7 push esi
00413BA8 push edi
switch (a)
00413BA9 mov eax,dword ptr [a]
00413BAC mov dword ptr [ebp-44h],eax
00413BAF mov ecx,dword ptr [ebp-44h]
00413BB2 sub ecx,1
00413BB5 mov dword ptr [ebp-44h],ecx
00413BB8 cmp dword ptr [ebp-44h],3
00413BBC ja $LN7+0Dh (0413C02h)
00413BBE mov edx,dword ptr [ebp-44h]
00413BC1 jmp dword ptr [edx*4+413C0Ch]
{
case 1:
printf("结果为1\n");
00413BC8 push offset string "\xbd\xe1\xb9\xfb\xce\xaa1\n" (0415B34h)
00413BCD call _printf (04110AFh)
00413BD2 add esp,4
break;
00413BD5 jmp $LN7+0Dh (0413C02h)
case 2:
printf("结果为2\n");
00413BD7 push offset string "\xbd\xe1\xb9\xfb\xce\xaa2\n" (0415B40h)
00413BDC call _printf (04110AFh)
00413BE1 add esp,4
break;
00413BE4 jmp $LN7+0Dh (0413C02h)
case 3:
printf("结果为3\n");
00413BE6 push offset string "\xbd\xe1\xb9\xfb\xce\xaa3\n" (0415B4Ch)
00413BEB call _printf (04110AFh)
00413BF0 add esp,4
break;
00413BF3 jmp $LN7+0Dh (0413C02h)
case 4:
printf("结果为4\n");
00413BF5 push offset string "\xbd\xe1\xb9\xfb\xce\xaa4\n" (0415B58h)
00413BFA call _printf (04110AFh)
00413BFF add esp,4
break;
default:
break;
}
}
00413C02 pop edi
00413C03 pop esi
00413C04 pop ebx
00413C05 mov esp,ebp
00413C07 pop ebp
00413C08 ret
00413C09 nop dword ptr [eax]
00413C0C enter 413Bh,0
00413C10 xlat byte ptr [ebx]
00413C11 cmp eax,dword ptr [ecx]
00413C14 out 3Bh,al
00413C16 inc ecx
00413C17 add ch,dh
00413C19 cmp eax,dword ptr [ecx]
case语句的条件差值不大
对于case比较复杂的语句,编译器采用了两个表来处理。
一个是case地址表,另一个是case的地址索引表。
意思就是相当于地址索引表中存的是地址表的首地址
地址表
地址表中是从0开始作为索引,而且是用最大的case语句的条件值作为最大的数组范围,如果在其中有的是没有的,那么就用default或者switch的返回语句来填充
比如:
switch(a)
{
case 1:
case 3:
case 100:
case 101:
default:
}
这个就是其中的case2是没有的,但是地址表中依然会填充一个default或者switch结束的下一条语句的地址。然后地址表中会自动吧case的条件值进行排序,所以不需要对case值进行排序来使用
索引表
针对于case语句差距比较大,需要多个索引来处理,比如
switch(a)
{
case 1:
case 1000:
}
这里如果还是用地址表来存储,就需要1000个地址来处理了,所以这里引入了索引表
地址表还是有几个case存几个case,当然如果有default也要存,没有default就存一个switch结束语句的下一条语句的地址
然后索引表保存的是case的数组的数组寻址地址
比如这里就是
int a[1000];//假设a是索引表
a[0] = 0; //这里的索引表的数值表示的是地址表的索引的值
a[1]~a[999] = 不存在的case的下标
然后a[1000] = 1;//表示case 1000的索引表的下标
所以这里来讲上面那个比较复杂的case语句代码反汇编再来看就比较简单了,
switch (a)
00413BA9 mov eax,dword ptr [a]
00413BAC mov dword ptr [ebp-44h],eax
00413BAF mov ecx,dword ptr [ebp-44h]
00413BB2 sub ecx,1
00413BB5 mov dword ptr [ebp-44h],ecx
00413BB8 cmp dword ptr [ebp-44h],3
00413BBC ja $LN7+0Dh (0413C02h)
00413BBE mov edx,dword ptr [ebp-44h]
00413BC1 jmp dword ptr [edx*4+413C0Ch]
首先将switch中的条件变量赋值给一个寄存器,这里是赋值给的ecx,然后ecx-1。这里的减一是为了跟地址表平齐,因为数组是从0开始数数的所以要减1。然后把条件变量的值跟最大的case条件语句进行比较好进行ja的JCC指令跳转,ja的意思是无符号数比较,左边大于右边就跳转。如果输入的是0或者是负数也是比这个大的所以也会跳转,而这里的跳转就直接跳转到default或者是switch语句的结束的下一条语句的地址。然后再进行把该条件变量的值减1后的值赋值给一个寄存器来保管,再通过相对基址寻址访问索引表再访问到地址表来跳转。
细心的你肯定发现,如果差的值很大会不会浪费?肯定是会浪费的于是有了另外一种处理办法。
case语句的条件差值很大
当最大的case值和最小的case值差距大于255的时候就会造成空间的浪费,所以这里采用一颗平衡二叉树来解决