【新书推荐】6.3 switch语句

news2024/11/13 12:07:56

本节必须掌握的知识点:

    示例代码二十一

        代码分析

        汇编解析

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等类型。

  1. Option必须是常量表达式,不能是变量;
  2. 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", daysystem("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");的语句。

练习

  1. 请读者书写程序6-3-2.c伪代码,并绘制流程图。
  2. 请读者将6-3-2.c翻译成汇编语言实现。
  3. 请读者分析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]地址处

 

总结

  1. 分支少于4的时候,用switch没意义,因为编译器会生成类似if else 语句;
  2. case后面的常量可以是无序的,并不影响大表的生成;【这个可以自行测试】
  3. case option的值连续、相近会大大提高效率;
  4. 将连续的10项中去掉去7项以上,不要去掉最大值和最小值,会生成小表。
  5. default语句中的break可以省略。但是为了养成良好的习惯,建议不要省略。
  6. 在已明确没有其他任何情形的情况下,可以省略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语言》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1437529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

情报搜集神器:Spiderfoot 保姆级教程

一、介绍 SpiderFoot是一款开源的情报搜集和足迹分析工具&#xff0c;用于自动化收集有关目标的信息。它被设计为一种开放式情报&#xff08;OSINT&#xff09;工具&#xff0c;可以帮助安全专业人员、渗透测试人员和研究人员收集、分析和汇总来自互联网的信息。 以下是 Spid…

java的excel列行合并模版

1.效果 2.模版 <tableborder"1"cellpadding"0"cellspacing"0"class"tablebor"id"TABLE"><tr align"center" class"bg217"><td style"background-color: #008000; color: #ffffff;p…

华为数通方向HCIP-DataCom H12-821题库(单选题:441-460)

第441题 下面是一台路由输出的信息,关于这段信息描述正确的是 <R1>display bgp peerBGP local router ID : 2.2.2.2Local AS number : 100Total number of peers : 2 Peers in established state : 0Peer V AS MsgRcvd MsgSent OutQ Up/Down …

fgcvbnm

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 磁盘满的本质分析 专栏&#xff1a;《Linux从小白到大神》 | 系统学习Linux开发、VIM/GCC/GDB/Make工具…

离谱!英国大学53%本科生用AI写论文!留学生该如何面对AI冲击?

随着AI的不断强化和更新换代&#xff0c;越来越多的学生开始使用人工智能辅助写论文&#xff0c;能省去不少信息搜集的时间。 英国大学从最开始的明令禁止&#xff0c;到如今也在逐步接受学生将它用做辅助工具&#xff1a; 然而&#xff0c;AI的使用其实还存在很多问题&#xf…

蓝桥杯省赛无忧 课件137 第20次学长直播带练配套课件

01 树上异或 02 森林的最大美丽值 03 古老文明的数字仪式

3、生成式 AI 如何帮助您改进数据可视化图表

生成式 AI 如何帮助您改进数据可视化图表 使用生成式 AI 加速和增强数据可视化。 图像来源:DALLE 3 5 个关键要点: 数据可视化图表的基本结构使用 Python Altair 构建数据可视化图表使用 GitHub Copilot 加快图表生成速度使用 ChatGPT 为您的图表生成相关内容使用 DALL-E 将…

Token、CAS、JWT和OAuth 2.0认证系统认证中心系统设计对比与实践总结

在现代应用开发中&#xff0c;身份认证是一个关键的问题。为了解决身份认证的需求&#xff0c;开发人员可以选择不同的认证系统&#xff0c;如Token、CAS&#xff08;Central Authentication Service&#xff09;和JWT&#xff08;JSON Web Token&#xff09;OAuth 2.0认证系统…

“掌握温度,感知湿度,一触即知!”DHT11温湿度传感器,为您的生活增添一份关怀与精准。#非标协议【下】

“掌握温度&#xff0c;感知湿度&#xff0c;一触即知&#xff01;”DHT11温湿度传感器&#xff0c;为您的生活增添一份关怀与精准。#非标协议【下】 前言预备知识1.DHT11温湿度传感器初识1.1产品概述1.2与51单片机接线1.3数据传送逻辑和数据格式 2.发送时序检测DHT11温湿度传感…

无心剑小诗《醉爱平凡人生》

醉爱平凡人生 平凡人生&#xff0c;别样卓越 做调色板上最亮的颜料 没有豪华光环与繁杂束缚 只有一份简单的快乐 不追求虚名&#xff0c;不被物欲左右 安静地享受生活&#xff0c;品味每滴雨露 平凡人生&#xff0c;宛如流淌的小溪 没有壮烈激流&#xff0c;却有恒久细流 不…

Java中处理I/O操作的不同方式:BIO,NIO,AIO

Java中处理I/O操作的不同方式&#xff1a;BIO&#xff0c;NIO&#xff0c;AIO 亲爱的朋友&#xff0c; 在这美好的时刻&#xff0c;愿你感受到生活的温暖和欢乐。愿你的每一天都充满着笑容和满足&#xff0c;无论面对什么挑战都能勇往直前&#xff0c;化解困境。 希望你的心中充…

LeetCode:26.删除有序数组中的重复项

26. 删除有序数组中的重复项 - 力扣&#xff08;LeetCode&#xff09; 目录 题目&#xff1a; 思路&#xff1a; 代码注释&#xff1a; 每日表情包&#xff1a; 题目&#xff1a; 思路&#xff1a; 没啥特殊的&#xff0c;老老实实双指针遍历数组&#xff0c;&#xff0…

2024.2.6日总结(小程序开发3)

页面配置 页面配置和全局配置的关系&#xff1a; 小程序中&#xff0c;app.json中的window节点&#xff0c;可以全局配置小程序中每个页面的窗口表现 如果某些小程序想要有特殊的窗口表现&#xff0c;可以用页面级别的.json配置文件实现这个需求 页面配置和全局配置冲突时&…

企业计算机服务器中了mallox勒索病毒怎么办,mallox勒索病毒处理流程

由于网络技术的不断发展与应用&#xff0c;越来越多的企业开始依赖计算机技术来提高企业效率。然而&#xff0c;网络安全威胁无处不在&#xff0c;严重影响着企业计算机服务器中的数据安全。近期&#xff0c;云天数据恢复中心接到许多中大型企业的求助&#xff0c;企业的多台服…

国产数据库 GBase 8a 安装

一、GBase简介 GBase 是南大通用数据技术有限公司推出的自主品牌的数据库产品&#xff0c;在国内数据库市场具有较高的品牌知名度。 二、下载地址&#xff08;需要先注册账号&#xff09; https://www.gbase.cn/download/gbase-8a?categoryINSTALL_PACKAGE 三、环境 服务…

Python(20)正则表达式(Regular Expression)中常用函数用法

大家好&#xff01;我是码银&#x1f970; 欢迎关注&#x1f970;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 正文 正则表达式 粗略的定义&#xff1a;正则表达式是一个特殊的字符序列&#xff0c;帮助用户非常便捷的检查一个字符串是否符合某种模…

小白代码审计入门

最近小白一直在学习代码审计,对于我这个没有代码审计的菜鸟来说确实是一件无比艰难的事情。但是着恰恰应了一句老话:万事开头难。但是小白我会坚持下去。何况现在已经喜欢上了代码审计,下面呢小白就说一下appcms后台模板Getshell以及读取任意文件,影响的版本是2.0.101版本。…

vue项目开发vscode配置

配置代码片段 步骤如下&#xff1a; 文件->首选项->配置用户代码片段新增全局代码片段起全局代码片段文件名“xxx.code-snippets” 这里以配置vue2初始代码片段为例&#xff0c;配置具体代码片段 {"name": "vue-sph","version": "…

【Qt】常见问题

1.存在未解析的标识符 将build文件夹删掉重新编译。 2.左侧项目目录栏无法删除已添加项目 打开目标项目上一级的pro文件&#xff0c;将目标文件名字注释或者删除掉&#xff0c;最后保存&#xff0c;qt就会自动更新&#xff0c;将该项目隐藏掉。 3.在qt creator下添加槽函数…

MATLAB语音去噪系统

目录 一、背景 二、GUI页面 三、程序 3.1 LMS滤波程序 3.2 GUI程序 四、附录 一、背景 本文介绍了一种最佳的自适应滤波器结构&#xff0c;该结构采用最小均方差&#xff08;LMS&#xff09;作为判据&#xff0c;通过不断迭代自适应结构来调整得到最佳滤波器…