本节必须掌握的知识点:
示例代码二十一
代码分析
汇编解析
6.3.1 示例二十一
■switch语句形式
switch语句是一种多路判断语句,会根据不同的选项,跳转到不同的分支语句。
switch语句的语法格式:
switch(Controlling Expression)
{
case Option1:
break;
case Option2:
break;
case Option3:
break;
default:
break;
}
●语法解析:
Contronlling Expression控制表达式,其中表达式的值必须是整型,Option必须是常量表达式。用户根据输入的Contronlling Expression的整型值,寻找对应的case Option。如果Contronlling Expression的值与case 下OptionX的值相等,则跳转到OptionX,执行case OptionX对应的语句块。如果没有找到对应的OptionX,则执行default对应的语句块。 break
表示:如果执行了break语句则导致程序立即结束退出对应的switch语句块。
注意
1.Contronlling Expression必须是整型,不能为其他类型;整型包括:char、short、int等类型。
- Option必须是常量表达式,不能是变量;
- case 后面的值不能相同;
4.case 与 default 以冒号结尾,示范:case 1:
5.case 与 default 在switch语句中的作用只是一个标签。
示例代码二十一
●第一步:分析需求,设计程序结构框架。
分析需求:一周天气预报,周一晴天、周二阴天、周三下雨、周四下雪、其他时间雨夹雪。
设计程序结构框架:分支结构(switch语句)。
●第二步:数据定义,定义恰当的数据结构;
int day = 0;//定义一个int类型的整型局部变量,并初始化为0。
●第三步:分析算法。
根据整型变量day的值判断执行语句。
●第四步:编写伪代码,即用我们自己的语言来编写程序。
int main(void) {
定义一个int类型整型变量day;
调用printf函数打印一个提示信息:
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
调用scanf_s函数接收键盘输入一个整数,并存入变量day;
switch (day)
如果day=1:调用printf函数输出"周一天气:晴天\n";
如果day=2:调用printf函数输出"周二天气:阴天\n";
如果day=3:调用printf函数输出"周三天气:大雨\n";
如果day=4:调用printf函数输出"周四天气:中雪\n";
其他情形周(五、六、日):调用printf函数输出"周%d天气:雨夹雪\n", day; system("pause");
return 0;
}
●第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图;
图6-10 示例二十一流程图
●第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
/*
输出一周的天气预报
周一晴天、周二阴天、周三下雨、周四下雪、其他时间雨夹雪。
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 0;
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
scanf_s("%d", &day);
switch (day)
{
case 1:
printf("周一天气:晴天\n");
break;
case 2:
printf("周二天气:阴天\n");
break;
case 3:
printf("周三天气:大雨\n");
break;
case 4:
printf("周四天气:中雪\n");
break;
default:
printf("周%d天气:雨夹雪\n", day);
break;
}
system("pause");
return 0;
}
●输出结果:
☆☆☆☆☆天气查询☆☆☆☆☆
☆查询周一天气请输入:1
☆查询周二天气请输入:2
☆查询周三天气请输入:3
☆查询周四天气请输入:4
☆查询周五天气请输入:5
☆查询周六天气请输入:6
☆查询周天天气请输入:7
1
周一天气:晴天
●第七步:调试程序,修复程序中可能出现的BUG;
参见反汇编代码。
●第八步:优化代码,尝试更好的设计方案,效率更高的算法,逻辑更为清晰简洁明了。假如我们输入的整数是9,那么我们会发现输出结果为:
9
周9天气:雨夹雪
很显然这不是我们想要的结果。怎么解决这个问题呢?应该在scanf_s输入整数的时候做一个检查,输入的整数值只能在1~7之间,输入其他整数值应提示输入错误,并且重新输入一个正确的值。我们将在下一章7.1节中讲解具体实现的方法。
6.3.2 代码分析
■输入1的情况:
1
周一天气:晴天
当用户执行程序,输入1时,day = 1。程序执行switch(day) ,拿着用户输入的1与switch语句块里的case option匹配,匹配到case 1,则执行case 1对应的语句块printf("周一天气:晴天\n");打印出周一天气:晴天,执行break,跳出switch语句,执行return 0;结束程序。
■输入5的情况:
5
周5天气:雨夹雪
当用户执行程序,输入5时,day = 5;程序执行switch(day) ,拿着用户输入的1与switch语句块里的case option匹配,没有在case option 中找到5,则执行default对应的语句块printf("周%d天气:雨夹雪\n",day);打印出周5天气:雨夹雪,执行break,跳出switch语句,执行return 0;结束程序。
6.3.3 汇编解析
■汇编代码
;C标准库头文件和导入库
include vcIO.inc
.data
day sdword ?
.const
szMsg1 db 09h,09h,"☆☆☆☆☆天气查询☆☆☆☆☆",0dh,0ah,0
szMsg2 db 09h,09h," ☆查询周一天气请输入:1",0dh,0ah,0
szMsg3 db 09h,09h," ☆查询周二天气请输入:2",0dh,0ah,0
szMsg4 db 09h,09h," ☆查询周三天气请输入:3",0dh,0ah,0
szMsg5 db 09h,09h," ☆查询周四天气请输入:4",0dh,0ah,0
szMsg6 db 09h,09h," ☆查询周五天气请输入:5",0dh,0ah,0
szMsg7 db 09h,09h," ☆查询周六天气请输入:6",0dh,0ah,0
szMsg8 db 09h,09h," ☆查询周日天气请输入:7",0dh,0ah,0
szMsg9 db "%d",0
szMsg10 db "周一天气:晴天",0dh,0ah,0
szMsg11 db "周二天气:阴天",0dh,0ah,0
szMsg12 db "周三天气:大雨",0dh,0ah,0
szMsg13 db "周四天气:中雪",0dh,0ah,0
szMsg14 db "周%d天气:雨夹雪",0dh,0ah,0
.code
start:
invoke printf,offset szMsg1
invoke printf,offset szMsg2
invoke printf,offset szMsg3
invoke printf,offset szMsg4
invoke printf,offset szMsg5
invoke printf,offset szMsg6
invoke printf,offset szMsg7
invoke printf,offset szMsg8
;输入整数day
invoke scanf,offset szMsg9,ADDR day
;
.if day == 1
invoke printf,offset szMsg10
.elseif day == 2
invoke printf,offset szMsg11
.elseif day == 3
invoke printf,offset szMsg12
.elseif day == 4
invoke printf,offset szMsg13
.else
invoke printf,offset szMsg14,ADDR day
.endif
;
invoke _getch
ret
end start
●输出结果:
同示例二十一
上述汇编使用.if/.elseif实现了与C语言中的switch语句相同的功能。
结论
switch语句本质就是if/else if语句的简化形式。
■反汇编代码
我们看下在反汇编中,switch语句是怎么表现的。在int day = 0;前下断点,F5切换到反汇编窗口。
在之前的章节中,我们都是一行行的往下推论每行指令都干了什么,这次switch语句的分析我们打乱顺序。因为当我们看到switch语句展现的反汇编,会让我们小白有些迷惑,不知道它到底再干什么,遇到这种情况,我们需要找一个入口点,从入口点切入,然后上下分析。switch语句的入口点是什么呢?就是case 1开始的地方,我们知道当用户输入1时,程序将自动寻找case 1,如果找到会有一个跳转的指令,如果没有找到会有一个调转到default的指令。也就是说,在case 1代码的上方是有两个跳转指令的。此时我们定位到case 1对应的反汇编代码,在case1上方有两个跳转指令。
ja指令:需要结合cmp指令的配合,如果对比的结果大于则跳转到内存地址0x00211A3C的位置,很明显内存地址0x00211A3C的位置是default的位置。
如果cmp对比的结果小于等于则继续执行下面的代码。我们知道jmp是无条件跳转指令,也就是必须跳转的。可它给出了我们的跳转地址是[edx*4+211AB0h],为什么是这样的地址?因为我们switch一共写了4个case分支语句。我们选择的分支不同,跳转的地址就会不同。
jmp上面一行指令,是将[ebp-0D4h]地址中的值赋给了edx,再往上的代码就是对[ebp-0D4h]地址指向的内存进行操做。
这时我们就要分析ja前面的代码了。请看每行代码下的分析。
scanf_s("%d", &day);接收键盘输入一个整数,并存入变量day=1
002119C1 lea eax,[day]
002119C4 push eax
002119C5 push offset string "%d" (0217C54h)
002119CA call _scanf_s (0211154h)
002119CF add esp,8
switch (day);switch结构
002119D2 mov eax,dword ptr [day] ;将变量day的值存入eax=1
002119D5 mov dword ptr [ebp-0D4h],eax ;eax的值存入[ebp-0D4H]
002119DB mov ecx,dword ptr [ebp-0D4h] ;[ebp-0D4H]的值存入ecx=1
002119E1 sub ecx,1 ;ecx-1=0
002119E4 mov dword ptr [ebp-0D4h],ecx ; ecx的值存入[ebp-0D4H]=0
002119EA cmp dword ptr [ebp-0D4h],3 ;比较[ebp-0D4h]和3的大小
002119F1 ja $LN7+0Fh (0211A3Ch) ; [ebp-0D4h]>3,则跳转到default
002119F3 mov edx,dword ptr [ebp-0D4h] ;[ebp-0D4h]<3,edx=[ebp-0D4h]=0
002119F9 jmp dword ptr [edx*4+211AB0h] ;跳转到 [edx*4+211AB0h]地址处
在内存1窗口输入地址0x00211AB0回车,如图6-11所示:
图6-11 查看内存地址0x00211AB0存储的值
计算[edx*4+211AB0h]存储的值:
如果day=1,edx=0,[0*4+211AB0h]存储的值为0x00211a00,为case 1的地址。
如果day=2,edx=1,[1*4+211AB0h]存储的值为0x00211a0f,为case 2的地址。
如果day=3,edx=2,[2*4+211AB0h]存储的值为0x00211a1e,为case 3的地址。
如果day=4,edx=3,[3*4+211AB0h]存储的值为0x00211a2d,为case 4的地址。
如果day=5,[ebp-0D4h]=4,4>3,跳转到default地址。
如果day=6,[ebp-0D4h]=5,5>3,跳转到default地址。
如果day=7,[ebp-0D4h]=6,6>3,跳转到default地址。
switch语句汇编代码执行流程如图6-12所示:
图6-12 switc语句汇编代码执行流程
{
case 1:
printf("周一天气:晴天\n");
00211A00 push offset string "\xd6\xdc\xd2\xbb\xcc\xec\xc6\xf8\xa3\xba\xc7\xe7\xcc\xec\n" (0217C58h)
00211A05 call _printf (021104Bh)
00211A0A add esp,4
break;
00211A0D jmp $LN7+20h (0211A4Dh) ;跳转到system("pause");
case 2:
printf("周二天气:阴天\n");
00211A0F push offset string "\xd6\xdc\xb6\xfe\xcc\xec\xc6\xf8\xa3\xba\xd2\xf5\xcc\xec\n" (0217C6Ch)
00211A14 call _printf (021104Bh)
00211A19 add esp,4
break;
00211A1C jmp $LN7+20h (0211A4Dh)
case 3:
printf("周三天气:大雨\n");
00211A1E push offset string "\xd6\xdc\xc8\xfd\xcc\xec\xc6\xf8\xa3\xba\xb4\xf3\xd3\xea\n" (0217C80h)
00211A23 call _printf (021104Bh)
00211A28 add esp,4
break;
00211A2B jmp $LN7+20h (0211A4Dh)
case 4:
printf("周四天气:中雪\n");
00211A2D push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (0217C94h)
00211A32 call _printf (021104Bh)
00211A37 add esp,4
break;
00211A3A jmp $LN7+20h (0211A4Dh)
default:
printf("周%d天气:雨夹雪\n", day);
00211A3C mov eax,dword ptr [day]
00211A3F push eax
00211A40 push offset string "\xd6\xdc%d\xcc\xec\xc6\xf8\xa3\xba\xd3\xea\xbc\xd0\xd1\xa9\n" (0217CA8h)
00211A45 call _printf (021104Bh)
00211A4A add esp,8
break;
}
system("pause");
00211A4D mov esi,esp
00211A4F push offset string "pause" (0217CC0h)
00211A54 call dword ptr [__imp__system (021B168h)]
00211A5A add esp,4
实验三十七:不写break形式的switch语句
在VS中新建项目6-3-2.c。
/*
不写break形式的switch语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 0;
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
scanf_s("%d", &day);
switch (day)
{
case 1:
printf("周一天气:晴天\n");
case 2:
printf("周二天气:阴天\n");
case 3:
printf("周三天气:大雨\n");
case 4:
printf("周四天气:中雪\n");
default:
printf("周%d天气:雨夹雪\n", day);
}
system("pause");
return 0;
}
●输出结果:
测试1
1
周一天气:晴天
周二天气:阴天
周三天气:大雨
周四天气:中雪
周1天气:雨夹雪
测试2
2
周二天气:阴天
周三天气:大雨
周四天气:中雪
周2天气:雨夹雪
测试3
3
周三天气:大雨
周四天气:中雪
周3天气:雨夹雪
测试4
9
周9天气:雨夹雪
结论
代码分析:此时switch语句块下对应的case 与default下都没有写break语句。我们输入:1则程序找到对应的 case option,输出case 1对应的语句,而且程序会继续执行完case 1:以后的语句块,直到switch语句结束。以此类推,当我们输入2的时候将会连续执行case2、case 3、case 4和default语句。
请读者仔细观察实验三十七的反汇编代码,会发现case语句执行后,缺少一个jmp到system("pause");的语句。
练习
- 请读者书写程序6-3-2.c伪代码,并绘制流程图。
- 请读者将6-3-2.c翻译成汇编语言实现。
- 请读者分析6-3-2.c的反汇编代码。
实验三十八:缺省情况的switch语句
在VS中新建项目6-3-3.c。
/*
缺省情况的switch语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 0;
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
scanf_s("%d", &day);
switch (day)
{
case 1:
printf("周一天气:晴天\n");
break;
case 4:
printf("周四天气:中雪\n");
break;
case 7:
printf("周四天气:中雪\n");
break;
default:
printf("周%d天气:雨夹雪\n", day);
break;
}
system("pause");
return 0;
}
●输出结果:
测试一
1
周一天气:晴天
测试二
2
周2天气:雨夹雪
测试三
3
周3天气:雨夹雪
●汇编解析:
switch (day)
012819D2 mov eax,dword ptr [day]
012819D5 mov dword ptr [ebp-0D4h],eax
012819DB cmp dword ptr [ebp-0D4h],1
012819E2 je main+0D8h (012819F8h) ;如果day=1,跳转到012819F8h地址处
012819E4 cmp dword ptr [ebp-0D4h],4
012819EB je main+0E7h (01281A07h) ;如果day=4,跳转到01281A07h地址处
012819ED cmp dword ptr [ebp-0D4h],7
012819F4 je main+0F6h (01281A16h) ;如果day=7,跳转到01281A16h地址处
012819F6 jmp main+105h (01281A25h) ;如果day=其他值,跳转到default地址处
{
case 1:
printf("周一天气:晴天\n");
012819F8 push offset string "\xd6\xdc\xd2\xbb\xcc\xec\xc6\xf8\xa3\xba\xc7\xe7\xcc\xec\n" (01287C58h)
012819FD call _printf (0128104Bh)
01281A02 add esp,4
break;
01281A05 jmp main+116h (01281A36h)
case 4:
printf("周四天气:中雪\n");
01281A07 push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (01287C6Ch)
01281A0C call _printf (0128104Bh)
01281A11 add esp,4
break;
01281A14 jmp main+116h (01281A36h)
case 7:
printf("周四天气:中雪\n");
01281A16 push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (01287C6Ch)
01281A1B call _printf (0128104Bh)
01281A20 add esp,4
break;
01281A23 jmp main+116h (01281A36h)
default:
printf("周%d天气:雨夹雪\n", day);
01281A25 mov eax,dword ptr [day]
01281A28 push eax
01281A29 push offset string "\xd6\xdc%d\xcc\xec\xc6\xf8\xa3\xba\xd3\xea\xbc\xd0\xd1\xa9\n" (01287C80h)
01281A2E call _printf (0128104Bh)
01281A33 add esp,8
break;
}
system("pause");
01281A36 mov esi,esp
结论
上述代码中,case 1到case 7中间并没有连贯,只有 1、4、7,其中2、3、5、6就是缺省的对象。
由上述反汇编代码可知:
缺省情况的switch语句,VS2017中采用了类似if/else if语句的形式直接跳转到case Option地址处。并没有采用地址表的方式跳转。
实验三十九:缺省情况的switch语句
在VS中新建项目6-3-4.c。
/*
缺省情况的switch语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 0;
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
scanf_s("%d", &day);
switch (day)
{
case 1:
printf("周一天气:晴天\n");
break;
case 4:
printf("周四天气:中雪\n");
break;
case 5:
printf("周四天气:中雪\n");
break;
case 7:
printf("周四天气:中雪\n");
break;
default:
printf("周%d天气:雨夹雪\n", day);
break;
}
system("pause");
return 0;
}
●输出结果:
测试一
1
周一天气:晴天
测试二
2
周2天气:雨夹雪
测试三
7
周四天气:中雪
switch (day)
009619D5 mov dword ptr [ebp-0D4h],eax ;day=1=[ebp-0D4h]
009619DB mov ecx,dword ptr [ebp-0D4h] ;ecx=[ebp-0D4h]=1
009619E1 sub ecx,1 ;ecx=0
009619E4 mov dword ptr [ebp-0D4h],ecx ;[ebp-0D4h]=ecx=0
009619EA cmp dword ptr [ebp-0D4h],6 ;比较[ebp-0D4h]和6
009619F1 ja $LN7+0Fh (0961A3Ch) ;如果[ebp-0D4h]>6,则跳转到default地址
009619F3 mov edx,dword ptr [ebp-0D4h] ;edx=[ebp-0D4h]=day-1
009619F9 jmp dword ptr [edx*4+961AB0h] ;跳转到[edx*4+961AB0h]地址处
Switch语句跳转地址表:
图6-13 实验三十九查看内存地址
day=1,edx=0,case 1地址为[0*4+961AB0h]= 0x00961a00;
day=2,edx=1,case 2地址为[1*4+961AB0h]= 0x00961a3c;缺失
day=3,edx=0,case 3地址为[2*4+961AB0h]= 0x00961a3c;缺失
day=4,edx=0,case 4地址为[3*4+961AB0h]= 0x00961a0f;
day=5,edx=0,case 5地址为[4*4+961AB0h]= 0x00961a1e;
day=6,edx=0,case 6地址为[5*4+961AB0h]= 0x00961a3c;缺失
day=7,edx=0,case 7地址为[6*4+961AB0h]= 0x00961a2d;
day=其他,[ebp-0D0h]>6跳转到default地址0x00961a3c;
{
case 1:
printf("周一天气:晴天\n");
00961A00 push offset string "\xd6\xdc\xd2\xbb\xcc\xec\xc6\xf8\xa3\xba\xc7\xe7\xcc\xec\n" (0967C58h)
00961A05 call _printf (096104Bh)
00961A0A add esp,4
break;
00961A0D jmp $LN7+20h (0961A4Dh)
case 4:
printf("周四天气:中雪\n");
00961A0F push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (0967C6Ch)
00961A14 call _printf (096104Bh)
00961A19 add esp,4
break;
00961A1C jmp $LN7+20h (0961A4Dh)
case 5:
printf("周四天气:中雪\n");
00961A1E push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (0967C6Ch)
00961A23 call _printf (096104Bh)
00961A28 add esp,4
break;
00961A2B jmp $LN7+20h (0961A4Dh)
case 7:
printf("周四天气:中雪\n");
00961A2D push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (0967C6Ch)
00961A32 call _printf (096104Bh)
00961A37 add esp,4
break;
00961A3A jmp $LN7+20h (0961A4Dh)
default:
printf("周%d天气:雨夹雪\n", day);
00961A3C mov eax,dword ptr [day]
00961A3F push eax
00961A40 push offset string "\xd6\xdc%d\xcc\xec\xc6\xf8\xa3\xba\xd3\xea\xbc\xd0\xd1\xa9\n" (0967C80h)
00961A45 call _printf (096104Bh)
00961A4A add esp,8
break;
}
system("pause");
00961A4D mov esi,esp
break;
}
结论
上述代码中,case 1到case 7中间并没有连贯,只有 1、4、5、7,其中2、3、6为缺省的对象。
由上述反汇编代码可知:
缺省情况的switch语句,VS2017中case地址超过3个以上时,采用与示例二十一相同的方式,在地址表内取偏移地址“[EDX*4+地址表基地址]”的方式跳转到case Option地址处。
实验四十:default语句写在case语句上面
在VS中新建项目6-3-5.c。
/*
default语句写在case语句上面
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 0;
printf("\t\t☆☆☆☆☆天气查询☆☆☆☆☆\n");
printf("\t\t ☆查询周一天气请输入:1\n");
printf("\t\t ☆查询周二天气请输入:2\n");
printf("\t\t ☆查询周三天气请输入:3\n");
printf("\t\t ☆查询周四天气请输入:4\n");
printf("\t\t ☆查询周五天气请输入:5\n");
printf("\t\t ☆查询周六天气请输入:6\n");
printf("\t\t ☆查询周日天气请输入:7\n");
scanf_s("%d", &day);
switch (day)
{
default:
printf("周%d天气:雨夹雪\n", day);
break;
case 1:
printf("周一天气:晴天\n");
break;
case 2:
printf("周二天气:阴天\n");
break;
case 3:
printf("周三天气:大雨\n");
break;
case 4:
printf("周四天气:中雪\n");
break;
}
system("pause");
return 0;
}
●输出结果:
测试一
1
周一天气:晴天
测试二
7
周7天气:雨夹雪
●反汇编代码分析:
switch (day)
012C19D2 mov eax,dword ptr [day]
switch (day)
012C19D5 mov dword ptr [ebp-0D4h],eax
012C19DB mov ecx,dword ptr [ebp-0D4h]
012C19E1 sub ecx,1
012C19E4 mov dword ptr [ebp-0D4h],ecx
012C19EA cmp dword ptr [ebp-0D4h],3
012C19F1 ja main+0E0h (012C1A00h) ;跳转到default语句
012C19F3 mov edx,dword ptr [ebp-0D4h]
012C19F9 jmp dword ptr [edx*4+12C1AB0h]
{
default:
printf("周%d天气:雨夹雪\n", day);
012C1A00 mov eax,dword ptr [day]
012C1A03 push eax
012C1A04 push offset string "\xd6\xdc%d\xcc\xec\xc6\xf8\xa3\xba\xd3\xea\xbc\xd0\xd1\xa9\n" (012C7C58h)
012C1A09 call _printf (012C104Bh)
012C1A0E add esp,8
break;
012C1A11 jmp $LN8+0Dh (012C1A4Dh)
case 1:
printf("周一天气:晴天\n");
012C1A13 push offset string "\xd6\xdc\xd2\xbb\xcc\xec\xc6\xf8\xa3\xba\xc7\xe7\xcc\xec\n" (012C7C70h)
012C1A18 call _printf (012C104Bh)
012C1A1D add esp,4
break;
012C1A20 jmp $LN8+0Dh (012C1A4Dh)
case 2:
printf("周二天气:阴天\n");
012C1A22 push offset string "\xd6\xdc\xb6\xfe\xcc\xec\xc6\xf8\xa3\xba\xd2\xf5\xcc\xec\n" (012C7C84h)
012C1A27 call _printf (012C104Bh)
012C1A2C add esp,4
break;
012C1A2F jmp $LN8+0Dh (012C1A4Dh)
case 3:
printf("周三天气:大雨\n");
012C1A31 push offset string "\xd6\xdc\xc8\xfd\xcc\xec\xc6\xf8\xa3\xba\xb4\xf3\xd3\xea\n" (012C7C98h)
012C1A36 call _printf (012C104Bh)
012C1A3B add esp,4
break;
012C1A3E jmp $LN8+0Dh (012C1A4Dh)
case 4:
printf("周四天气:中雪\n");
012C1A40 push offset string "\xd6\xdc\xcb\xc4\xcc\xec\xc6\xf8\xa3\xba\xd6\xd0\xd1\xa9\n" (012C7CACh)
012C1A45 call _printf (012C104Bh)
012C1A4A add esp,4
break;
结论
default语句块的位置并不会影响程序执行的流程。
实验四十一:缺省较多的switch语句
在VS中新建项目6-3-6.c。
/*
缺省较多的switch语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int day = 110;
switch (day)
{
case 100:
printf("100\n");
break;
case 108:
printf("109\n");
break;
case 109:
printf("109\n");
break;
case 110:
printf("110\n");
break;
default:
printf("其他\n");
break;
}
system("pause");
return 0;
}
●输出结果:
110
●反汇编代码分析:
int day = 110;
00EA1838 mov dword ptr [day],6Eh ;将110存入变量day中
switch (day)
00EA183F mov eax,dword ptr [day] ;eax=day
00EA1842 mov dword ptr [ebp-0D0h],eax ;[ebp-0D0h]=eax
00EA1848 mov ecx,dword ptr [ebp-0D0h] ;ecx=[ebp-0D0h]
00EA184E sub ecx,64h ;ecx=ecx-100=10
00EA1851 mov dword ptr [ebp-0D0h],ecx ;[ebp-0D0h]=ecx=10
00EA1857 cmp dword ptr [ebp-0D0h],0Ah ;比较[ebp-0D0h]和10的大小
00EA185E ja $LN7+0Fh (0EA18B0h) ;[ebp-0D0h]>10跳转到default地址处
00EA1860 mov edx,dword ptr [ebp-0D0h] ;edx=[ebp-0D0h]=10
00EA1866 movzx eax,byte ptr [edx+0EA1900h] ;eax=3
00EA186D jmp dword ptr [eax*4+0EA18ECh] ;跳转到[eax*4+0EA18ECh]地址处
图6-14 查看内存地址0EA1900h存储的值
day=100,edx=0,eax=0,case 100地址为[0*4+0EA18ECh]= 0x00ea1874;
day=101,edx=1,eax=4,case 101地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=102,edx=2,eax=4,case 102地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=103,edx=3,eax=4,case 103地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=104,edx=4,eax=4,case 104地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=105,edx=5,eax=5,case 105地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=106,edx=6,eax=6,case 106地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=107,edx=7,eax=7,case 107地址为[4*4+0EA18ECh]= 0x00ea18b0;缺失
day=108,edx=8,eax=1,case 108地址为[1*4+0EA18ECh]= 0x00ea1883;
day=109,edx=9,eax=2,case 109地址为[2*4+0EA18ECh]= 0x00ea1892;
day=110,edx=10,eax=3,case 110地址为[3*4+0EA18ECh]= 0x00ea18a1;
day=其他,[ebp-0D0h]>10跳转到default地址0x00ea18b0;
{
case 100:
printf("100\n");
00EA1874 push offset string "100\n" (0EA7B30h)
00EA1879 call _printf (0EA104Bh)
00EA187E add esp,4
break;
00EA1881 jmp $LN7+1Ch (0EA18BDh)
case 108:
printf("109\n");
00EA1883 push offset string "109\n" (0EA7B38h)
00EA1888 call _printf (0EA104Bh)
00EA188D add esp,4
break;
00EA1890 jmp $LN7+1Ch (0EA18BDh)
case 109:
printf("109\n");
00EA1892 push offset string "109\n" (0EA7B38h)
00EA1897 call _printf (0EA104Bh)
00EA189C add esp,4
break;
00EA189F jmp $LN7+1Ch (0EA18BDh)
case 110:
printf("110\n");
00EA18A1 push offset string "110\n" (0EA7B40h)
00EA18A6 call _printf (0EA104Bh)
00EA18AB add esp,4
break;
00EA18AE jmp $LN7+1Ch (0EA18BDh)
default:
printf("其他\n");
00EA18B0 push offset string "\xc6\xe4\xcb\xfb\n" (0EA7B48h)
00EA18B5 call _printf (0EA104Bh)
00EA18BA add esp,4
break;
}
system("pause");
00EA18BD mov esi,esp
break;
}
结论
实验四十一的反汇编代码与之前的实验有所不同。当缺省较多的switch语句时,会先建立一个较小的表,如图6-14所示:较小的表的基址为0EA1900h,在内存中以字节为单位,存储11个值,如下所示。
第一步:先取edx的值为day值与第一个case值100的差值。
00EA1860 mov edx,dword ptr [ebp-0D0h] ;edx=[ebp-0D0h]=10
第二步:根据edx的值取对应的[edx+0EA1900h]地址处的值,并扩展为32位,存入eax。
00EA1866 movzx eax,byte ptr [edx+0EA1900h] ;eax=3
第三步:根据eax的值,取case地址表中的偏移地址,case地址表的基址为0EA18Ech,偏移地址为eax*4:
00EA186D jmp dword ptr [eax*4+0EA18ECh] ;跳转到[eax*4+0EA18ECh]地址处
总结
- 分支少于4的时候,用switch没意义,因为编译器会生成类似if else 语句;
- case后面的常量可以是无序的,并不影响大表的生成;【这个可以自行测试】
- case option的值连续、相近会大大提高效率;
- 将连续的10项中去掉去7项以上,不要去掉最大值和最小值,会生成小表。
- default语句中的break可以省略。但是为了养成良好的习惯,建议不要省略。
- 在已明确没有其他任何情形的情况下,可以省略default语句。
练习
1、请大家思考一下,下面代码会生成怎么样的反汇编,请先在纸上推论一下,接着再VS反汇编代码中观看,并考虑如何进行优化。
#include <stdio.h>
#include <windows.h>
int main(void)
{
int day =1 0;
switch(day)
{
case 205:
printf("%d\n",day);
break;
case 206:
printf("%d\n",day);
break;
case 207:
printf("%d\n",day);
break;
case 208:
printf("%d\n",day);
break;
case 209:
printf("%d\n",day);
break;
case 210:
printf("%d\n",day);
break;
case 10:
printf("%d\n",day);
break;
default:
printf("%d\n",day);
break;
}
system("pause");
return 0;
}
2、用程序实现简单的四则运算器,要求输入类似a+b然后确定,系统计算出结果C,并以“a+b=c”的形式输出。注:a、b、c是整数。
3、思考switch语句高效吗?为什么,请证明。
4、显示所输入整数除以3的余数。
5、使用switch语句判断输入的整数是奇数还是偶数。
6、判断输入的月份是哪个季节。
本文摘自编程达人系列教材《汇编的角度——C语言》。