.text:00411E45 mov [ebp+var_8], 1 ; int nTest = 1; 函数参数赋值
.text:00411E4C cmp [ebp+var_8], 0 ; 比较大小
.text:00411E50 jle short loc_411E61 ; 前一个值小于或等于后一个值,则跳转;否则,指令继续按顺序执行。
.text:00411E52 push offset aHelloWorld ; "Hello World!\r\n"
.text:00411E57 call sub_4113C5 ; 函数调用结束后,就只剩下函数参数还未平栈
.text:00411E5C add esp, 4 ; 平栈
.text:00411E5F jmp short loc_411E6E ; 跳转回return 0执行
.text:00411E61 ; ---------------------------------------------------------------------------
.text:00411E61
.text:00411E61 loc_411E61: ; CODE XREF: _main_0+30↑j
.text:00411E61 push offset aHelloEverybody ; "Hello everybody!\r\n"
.text:00411E66 call sub_4113C5
.text:00411E6B add esp, 4
.text:00411E6E
.text:00411E6E loc_411E6E: ; CODE XREF: _main_0+3F↑j
.text:00411E6E xor eax, eax
.text:00411E70 pop edi
.text:00411E71 pop esi
.text:00411E72 pop ebx
.text:00411E73 add esp, 0CCh
jle的跳转条件是 前一个数小于等于后一个数
当执行到 "jle" 指令时,会检查处理器标志寄存器中的小于等于标志位(LE)。如果 LE 为 1(被置位),表示前面的比较操作结果小于或等于零,此时程序将跳转到指定的目标地址继续执行。如果 LE 为 0(未被置位),则跳转不会发生,程序将顺序执行下一条指令
和Debug区别在于只剩下一个分支,经过了编译优化;编译器是被cmp比较后是一个常量,就减掉了那个不会被执行的分支
cmp xxxx
jcc 地址
_if
XXXX
jmp ends
_else
XXXX
_end
xor eax,eax
xxx
ret
程序流程图
jnz跳转条件:jnz指令用于判断前一个操作的结果是否为零,如果结果不为零,则执行跳转操作。
当执行到 "jnz" 指令时,会检查处理器标志寄存器中的零标志位(ZF)。如果 ZF 为 0(未被置位),表示前面的操作结果不为零,此时程序将跳转到指定的目标地址继续执行。如果 ZF 为 1(被置位),则跳转不会发生,程序将顺序执行下一条指令。
汇编执行逻辑
.text:00411E4C cmp [ebp+var_8], 0
.text:00411E50 jle short loc_411E61
.text:00411E52 push offset aHelloWorld ; "Hello World!\r\n"
.text:00411E57 call sub_4113C5
.text:00411E5C add esp, 4
.text:00411E5F jmp short loc_411E83 ; 若结果小于等于,就跳转
.text:00411E61 ; ---------------------------------------------------------------------------
.text:00411E61
.text:00411E61 loc_411E61: ; CODE XREF: _main_0+30↑j
.text:00411E61 cmp [ebp+var_8], 0
.text:00411E65 jnz short loc_411E76 ; 若结果不等于0则跳转
.text:00411E67 push offset aHelloEverybody ; "Hello everybody!\r\n"
.text:00411E6C call sub_4113C5
.text:00411E71 add esp, 4
.text:00411E74 jmp short loc_411E83
.text:00411E76 ; ---------------------------------------------------------------------------
.text:00411E76
.text:00411E76 loc_411E76: ; CODE XREF: _main_0+45↑j
.text:00411E76 push offset aHello ; "Hello "
.text:00411E7B call sub_4113C5
.text:00411E80 add esp, 4
.text:00411E83
.text:00411E83 loc_411E83: ; CODE XREF: _main_0+3F↑j
.text:00411E83 ; _main_0+54↑j
.text:00411E83 xor eax, eax
其发布版也是进行了优化
cmp XXXX
jcc _elseif else
_if
XXX
jmp _end
_elseif:
cmp XXX
jcc _else
XXXX
jmp _end
else:
XXXX
_end:
xor eax,eax
ret
使用其他汇编指令来优化
测试代码
#include <stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 1:
printf("n==1");
break;
case 2:
printf("n==2");
break;
case 3:
printf("n==3");
break;
}
return 0;
}
当执行到 "jz" 指令时,会检查处理器标志寄存器中的零标志位(ZF)。如果 ZF 为 1(被置位),表示前面的操作结果为零,此时程序将跳转到指定的目标地址继续执行。如果 ZF 为 0(未被置位),则跳转不会发生,程序将顺序执行下一条指令。
反汇编分析
.text:004150FF mov dword ptr [ebp+n], 0 ; int n = 0;
.text:00415106 lea eax, [ebp+n]
.text:00415109 push eax ; char
.text:0041510A push offset aD ; 这是个全局变量
.text:0041510F call sub_4113E3 ; 调用scanf函数
.text:00415114 add esp, 8 ; 平栈
.text:00415117 mov eax, dword ptr [ebp+n] ; switch (n)
.text:0041511A mov [ebp+var_D4], eax ; switch (n),局部变量之间赋值也是借助寄存器
.text:00415120 cmp [ebp+var_D4], 1 ; case 1
.text:00415127 jz short loc_41513D ; ZF=1,跳转,说明两个比较值相等;ZF=0,反之
.text:00415129 cmp [ebp+var_D4], 2 ; case 2
.text:00415130 jz short loc_41514C
.text:00415132 cmp [ebp+var_D4], 3 ; case 3
.text:00415139 jz short loc_41515B
.text:0041513B jmp short loc_415168 ; 如果三个都没匹配到,就会自导跳转到为末尾
.text:0041513D ; ---------------------------------------------------------------------------
.text:0041513D
.text:0041513D loc_41513D: ; CODE XREF: _main_0+57↑j
.text:0041513D push offset aN1 ; "n==1"
.text:00415142 call sub_4113C5 ; 调用printf
.text:00415147 add esp, 4
.text:0041514A jmp short loc_415168
.text:0041514C ; ---------------------------------------------------------------------------
.text:0041514C
.text:0041514C loc_41514C: ; CODE XREF: _main_0+60↑j
.text:0041514C push offset aN2 ; "n==2"
.text:00415151 call sub_4113C5 ; 调用printf
.text:00415156 add esp, 4
.text:00415159 jmp short loc_415168
.text:0041515B ; ---------------------------------------------------------------------------
.text:0041515B
.text:0041515B loc_41515B: ; CODE XREF: _main_0+69↑j
.text:0041515B push offset aN3 ; "n==3"
.text:00415160 call sub_4113C5 ; 调用printf
.text:00415165 add esp, 4
.text:00415168
.text:00415168 loc_415168: ; CODE XREF: _main_0+6B↑j
.text:00415168 ; _main_0+7A↑j ...
.text:00415168 xor eax, eax
.text:0041516A push edx
.text:0041516B mov ecx, ebp ; Esp
.text:0041516D push eax
.text:0041516E lea edx, Fd ; Fd
.text:00415174 call j_@_RTC_CheckStackVars@8 ; _RTC_CheckStackVars(x,x)
当case语句块小于4时,switch语句所反汇编的代码有区别于if else,if else是在判断语句后紧跟代码 块,而switch语句是将所有的代码块放置在一起,上半部分是判断语句,下半块是代码块。效率和if else相同
测试代码
#include <stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 1:
printf("n==1");
break;
case 2:
printf("n==2");
break;
case 3:
printf("n==3");
break;
case 5:
printf("n==5");
break;
}
return 0;
}
输入参数N传入switch的n
.text:00415117 mov eax, dword ptr [ebp+n] ; switch (n)
.text:0041511A mov [ebp+switch_n], eax ; switch (n)
.text:00415120 mov ecx, [ebp+switch_n] ; switch (n)
把输入的n减1
.text:00415126 sub ecx, 1
把减去的值赋值给ecx,并且与case最大值减一比较(5-1)
.text:00415129 mov [ebp+switch_n], ecx
.text:0041512F cmp [ebp+switch_n], 4
倘若前者大于后者,就跳转,说明超过了case的界限
.text:00415136 ja short def_41513E ; switch_n大于4就跳转
对于case数大于4,且线性结构,编译器就会使用比例因子寻址
.text:00415138 mov edx, [ebp+switch_n]
.text:0041513E jmp ds:jpt_41513E[edx*4] ; switch jump
地址jpt_41513E这里存储了一个跳转表,会根据edx*4进行偏移
4个case语句一共5个地址,其中第四个地址是为了补上case 4的缺失,指向的是switch语句的结束位 置。让整个表构成有序的线性结构,从而提高执行效率。
这个缺失的地址必须补上,如果没有补上跳转,则无法用数组寻址的方式跳转到对应的case语句,整个 跳转表从未补齐的那一个地址开始全部都是错误的。
找到对应的地址进行跳转
测试代码
#include <stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 5:
printf("n==1");
break;
case 6:
printf("n==2");
break;
case 7:
printf("n==3");
break;
case 10:
printf("n==5");
break;
}
return 0;
}
查看核心反汇编代码
把传入的switch_n - case(max);之后和最大的case(5)比较,前者大于后者,则跳转;然后跳转至跳转表;
测试代码
#include <stdio.h>
int main()
{
int n = 0;
scanf_s("%d", &n);
switch (n)
{
case 5:
printf("n==1");
break;
case 7:
printf("n==2");
break;
case 9:
printf("n==3");
break;
case 11:
printf("n==5");
break;
case 6:
printf("n==6");
break;
case 255:
printf("n==255");
break;
}
return 0;
}
查看反汇编核心代码
传入的switch_n减去case(5),之后和case(max)比较,大于则跳转;
把这个值放到edx中,把它作为下标,在byte_41520C这个数组中取值
这里也有一张表,这张表叫索引表。索引表中保存的是跳转表的数组下标。
case 5->0
case 6->1
case 7->2
case 8->6 因为没有case8 所以这里被置成了6
case 9->3
.....
因为这种无序的switch case会有一个索引不方便的问题,所以说他设计了一张索引表,把所有的case 后面跟的数字都制作成下标统一放在这个索引表里面。
这样的话就有一个好处,不管你的case语句后面跟着的数值是多少,我都能直接拿到我要的索引。
比如说
case 9 减去5=6
那么我就找这张表下标为6的数值,取出来进行寻址
这样就可以把4取出来了。
索引表就解决了case语句块无序的问题,这样就可以再用跳转表进行寻址了
.text:00415148 jmp ds:jpt_415148[eax*4] ; switch jump
然后用比例因子寻址的方式跳转到对应的地址
当case的值没有明显的线性关系时,就会出现两张表,一张索引表,保存下标,一张线性表,保
存地址
但是还有个效率的问题:无序线性结构会有两次查表的过程,因此效率会有所下降
当switch分支小于4时,其反汇编采用的结构和if else相同,两者效率一样。
当switch分支大于4时,switch采用跳转表的方式来执行代码块,此时效率比if else高。