`
successfulroof
  • 浏览: 73132 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

switch case 原理理解

 
阅读更多

/**************************/

/*转载   作者都不知道是谁。。。*/

/**************************/

当需要多次比较时,switch语句的效率比if-else if…… else语句(以后简称muti-if语句)的效率要高,这是我一直以来的理解,但是昨晚讨论到一个问题,这种“高效率”如何实现?今天早上又看到《更深入一点理解switch语句及c/c++对const的处理》和《透过IL看C# (1)switch语句》这两篇文章,前者(以后为[1])没有提及case语句中大跨度离散值的原理,后者(以后为[2])使用的离散数据量又比较小,而且该文侧重于用C#,由于不是很了解,不发表评论。

于是就写了一组程序,用gcc编译成汇编码(使用-S开关), 通过解读这些汇编码可以很好的帮助理解switch的原理。文中所涉及的环境为如下,Linux version 2.6.27.5-117.fc10.i686 (mockbuild@x86-7.fedora.phx.redhat.com) (gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC) ) #1 SMP Tue Nov 18 12:19:59 EST 2008(取自/proc/version)

1.三个数据的比较

程序1.1

int main(void)
{
    int i, n;
    switch(i){
        case 101:
            n = 1;
            break;
        case 102:
            n = 2;
            break;
        case 103:
            n = 3;
            break;
        default:
            n = 0;
            break;
    }
}

得到的汇编码1.1:

   .file    "switch.c"
    .text
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $24, %esp
    movl    -12(%ebp), %eax
    movl    %eax, -28(%ebp)
    cmpl    $102, -28(%ebp)
    je    .L4
    cmpl    $103, -28(%ebp)
    je    .L5
    cmpl    $101, -28(%ebp)
    jne    .L8
.L3:
    movl    $1, -8(%ebp)
    jmp    .L9
.L4:
    movl    $2, -8(%ebp)
    jmp    .L9
.L5:
    movl    $3, -8(%ebp)
    jmp    .L9
.L8:
    movl    $0, -8(%ebp)
.L9:
    addl    $24, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.3.2 20081105 (Red Hat 4.3.2-7)"
    .section    .note.GNU-stack,"",@progbits

这里可以看出switch语句的效率与muti-if语句的效率基本相当,[1]中认为:“gcc确实是把一些case语句转成了李维所说的那种方式进行处理,我们看见了代码中存在有众多的 cmpl 与 jmp 语句这就相当于你使用if..else..一样”,但是如果观察仔细的话,可以看出和if语句还是有区别的(事实上我第一次也没有细看,但是在后面的实验中,我发现了switch语句的优化,回过头来才发现), switch对比较的顺序自动进行了优化, cmpl的顺序与case的顺序是不同的, 先比较的是102, 然后才是103, 101,这就相当于我们人为的对muti-if语句进行了优先顺序调整,尽管结果可能与我们预期的不同,但是编译器的确这样做了, 这点在后面的实验中尤为明显。

在[2]中,作者说C#在3个数据,且数据连续的情况下造表,在数据取值比较不连续的情况下也是造表然后填空数据,在数据取值非常不连续的情况下和muti-if比较相同,而且顺序与case的顺序相同。该文中对switch(int)的探讨也至此完结。

2.五个数据的比较

程序2.1 五个连续数据

int main(void)
{
    int i, n;
    switch(i){
        case 101:
            n = 1;
            break;
        case 102:
            n = 2;
            break;
        case 103:
            n = 3;
            break;
        case 104:
            n = 4;
            break;
        case 105:
            n = 5;
            break;
        default:
            n = 0;
            break;
    }
}

汇编码2.1:

.file    "switch.c"
    .text
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $24, %esp
    movl    -12(%ebp), %eax
    subl    $101, %eax
    movl    %eax, -28(%ebp)
    cmpl    $4, -28(%ebp)
    ja    .L2
    movl    -28(%ebp), %edx
    movl    .L8(,%edx,4), %eax
    jmp    *%eax
    .section    .rodata
    .align 4
    .align 4
.L8:
    .long    .L3
    .long    .L4
    .long    .L5
    .long    .L6
    .long    .L7
    .text
.L3:
    movl    $1, -8(%ebp)
    jmp    .L11
.L4:
    movl    $2, -8(%ebp)
    jmp    .L11
.L5:
    movl    $3, -8(%ebp)
    jmp    .L11
.L6:
    movl    $4, -8(%ebp)
    jmp    .L11
.L7:
    movl    $5, -8(%ebp)
    jmp    .L11
.L2:
    movl    $0, -8(%ebp)
.L11:
    addl    $24, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.3.2 20081105 (Red Hat 4.3.2-7)"

可以看出,这里switch确实如同[1][2]中所述,编译器自造了.L8指向的表,表中标明了case跳转的入口,由此可见,这种情况下swithc效率确实比muti-if高,通过减去最小值所的的偏移来在表中寻找跳转入口

程序2.2 五个比较连续数据

int main(void)
{
    int i, n;
    switch(i){
        case 101:
            n = 1;
            break;
        case 103:
            n = 2;
            break;
        case 106:
            n = 3;
            break;
        case 109:
            n = 4;
            break;
        case 112:
            n = 5;
            break;
        default:
            n = 0;
            break;
    }
}

汇编码2,2:

   .file    "switch.c"
    .text
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $24, %esp
    movl    -12(%ebp), %eax
    subl    $101, %eax
    movl    %eax, -28(%ebp)
    cmpl    $11, -28(%ebp)
    ja    .L2
    movl    -28(%ebp), %edx
    movl    .L8(,%edx,4), %eax
    jmp    *%eax
    .section    .rodata
    .align 4
    .align 4
.L8:
    .long    .L3
    .long    .L2
    .long    .L4
    .long    .L2
    .long    .L2
    .long    .L5
    .long    .L2
    .long    .L2
    .long    .L6
    .long    .L2
    .long    .L2
    .long    .L7
    .text
.L3:
    movl    $1, -8(%ebp)
    jmp    .L11
.L4:
    movl    $2, -8(%ebp)
    jmp    .L11
.L5:
    movl    $3, -8(%ebp)
    jmp    .L11
.L6:
    movl    $4, -8(%ebp)
    jmp    .L11
.L7:
    movl    $5, -8(%ebp)
    jmp    .L11
.L2:
    movl    $0, -8(%ebp)
.L11:
    addl    $24, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.3.2 20081105 (Red Hat 4.3.2-7)"
    .section    .note.GNU-stack,"",@progbits

这里也如[2]所述, 建立了一个长度为case中最大值与最小值之差的表, case中未定义的则转到defalut来执行。

程序2.3 五个非常不连续的数据

 

int main(void)
{
    int i, n;
    switch(i){
        case 100:
            n = 1;
            break;
        case 120:
            n = 2;
            break;
        case 140:
            n = 3;
            break;
        case 160:
            n = 4;
            break;
        case 180:
            n = 5;
            break;
        default:
            n = 0;
            break;
    }
}

汇编码2.3:

 

    .file    "switch.c"
    .text
.globl main
    .type    main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl    -4(%ecx)
    pushl    %ebp
    movl    %esp, %ebp
    pushl    %ecx
    subl    $24, %esp
    movl    -12(%ebp), %eax
    movl    %eax, -28(%ebp)
    cmpl    $140, -28(%ebp)
    je    .L5
    cmpl    $140, -28(%ebp)
    jg    .L8
    cmpl    $100, -28(%ebp)
    je    .L3
    cmpl    $120, -28(%ebp)
    je    .L4
    jmp    .L2
.L8:
    cmpl    $160, -28(%ebp)
    je    .L6
    cmpl    $180, -28(%ebp)
    je    .L7
    jmp    .L2
.L3:
    movl    $1, -8(%ebp)
    jmp    .L11
.L4:
    movl    $2, -8(%ebp)
    jmp    .L11
.L5:
    movl    $3, -8(%ebp)
    jmp    .L11
.L6:
    movl    $4, -8(%ebp)
    jmp    .L11
.L7:
    movl    $5, -8(%ebp)
    jmp    .L11
.L2:
    movl    $0, -8(%ebp)
.L11:
    addl    $24, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.3.2 20081105 (Red Hat 4.3.2-7)"
    .section    .note.GNU-stack,"",@progbits

可以看到,这里switch又进行了二分法查找的优化, 而且当我把程序中“100, 120, 140, 160, 180”的顺序任意打乱后,得到的汇编码都是相同的由此可以推测,switch在编译时会先获得case中的各值,然后进行排序,最后生成使用二分法优化的查找比较模式。

另外,我推测使用造表法和二分查表法的应该是依据case中最大值与最小值之差与case语句个数来取舍的。

3 更多数据的比较

这里我又进行了更多条case语句的比较,程序源代码和生成的汇编码可以通过附件下载,通过比较,结论与我上面的推测基本相同。具体的原理相信在讲解C/C++编译器原理的书中有提及,希望有人找到后能够发送给我以共同探讨(email:yangyuan.whuyy@yahoo.com.cn)

4 为什么C/C++中switch语句需要的是整型或字符型

我们知道,在C中,字符型实际上也是作为整型来存储的,所以这个问题实际上是指switch语句只支持整型数据。由上面的讨论可以知道char的结果与整型应该是相同的

分享到:
评论

相关推荐

    编译原理实验报告-词法分析

    一、实验目的: (1)理解词法分析在编译程序中的作用; (2)掌握词法分析程序的实现方法和技术; ...1、关键字:while、if、else、switch、case 2、标识符 3、常数 4、+,-,*,/,,<,=,==,;

    编译原理 词法分析 源代码

     通过设计编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程,从输入的源程序中,识别出各个具有...

    编译原理实验报告 语法分析 语义分析 词法分析 详细的源程序

    实验目的:用c语言对一个简单语言的子集编制一个一遍扫描的编译程序,以加深对编译原理的理解,掌握编译程序的实现方法和技术。 语法分析 C2.1 实验目的 编制一个递归下降分析程序,实现对词法分析程序所提供的单词...

    redux-demo02:有助于理解redux原理的小例子-02

    redux原理-渲染篇 上一篇 ,用一个console方式讲解redux的原理,既然能在console打印出来,当然也可以在界面渲染出来。 下载&&启动 cd 新文件夹 ... switch(action.type){ case ADD_GUN: return state+

    语义分析&&编译原理实验

    通过上机实习,加深对语法制导翻译原理的理解,掌握将语法分析所识别的语法成分变换为中间代码的语义翻译方法。 二、实验要求 采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 ...

    编译原理实验报告和源程序

    实验目的:用c语言对一个简单语言的子集编制一个一遍扫描的编译程序,以加深对编译原理的理解,掌握编译程序的实现方法和技术。 语法分析 C2.1 实验目的 编制一个递归下降分析程序,实现对词法分析程序所提供的单词...

    单片机课程设计多功能定时器87547225.doc

    switch(P1&0x0f) { case 0x0e:KeyTemp=3;break; case 0x0d:KeyTemp=7;break; case 0x0b:KeyTemp=11;break; case 0x07:KeyTemp=15;break; case 0x0f:break; default:KeyTemp= 0x80;break; } P2&=0xf0; P2"=0x0d; ...

    C 程序指导书及实践指导

    1.理解和掌握多模块的程序设计与调试的方法。 2.掌握函数的定义和调用的方法。 3.学会使用递归方法进行程序设计。 [实验内容和步骤] 1. 编写一个函数,判断一个数是不是素数。在主函数中输入一个整数,输出是否是...

    操作系统课程设计1、分页方式的地址换算2、分段方式的地址换算

    本课程设计是学生学习完《计算机操作系统》课程后,进行的一次全面的综合训练,通过课程设计,让学生更好地掌握操作系统的原理及实现方法,加深对操作系统基础理论和重要算法的理解,加强学生的动手能力。 二、课程...

    实验一--词法分析实验报告.doc

    实验目的 设计、编制并调试一个词法分析程序,加深对词法分析原理的理解。 2. 实验要求 2.1 待分析的简单的词法 (1)关键字: begin if then while do end 所有的关键字都是小写。 (2)运算符和界符 : = + - * /...

    DS1302时钟芯片电子时钟显示C程序

    * 通过本例程了解 DS1302时钟芯片的基本原理和使用 ,理解并掌握DS1302时钟芯片 * * 驱动程序的编写以及实现数字字符在数码管中的显示。 * * *************************************************************...

    C++MFC教程

    |------ 1.2 理解Windows消息机制 |------ 1.3 利用Visual C++/MFC开发Windows程序的优势 |------ 1.4 利用MFC进行开发的通用方法介绍 |------ 1.5 MFC中常用类,宏,函数介绍 +-- 第二章 图形输出 |------ 2.1 和...

    C++编译器无法捕捉到的8种错误实例分析

    有助于深入理解C++运行原理,具体分析如下: 众所周知,C++是一种复杂的编程语言,其中充满了各种微妙的陷阱。在C++中几乎有数不清的方式能把事情搞砸。幸运的是,如今的编译器已经足够智能化了,能够检测出相当多的...

    【。net 专业】 面试题

    5.概述o/r mapping 的原理 利用反射,配置 将类于数据库表映射 7.用sealed修饰的类有什么特点 sealed 修饰符用于防止从所修饰的类派生出其它类。如果一个密封类被指定为其它类的基类,则会发生编译时错误。 密封类不...

    二叉排序树与平衡二叉树的实现

    使学生进一步理解和掌握课堂上所学各种基本抽象数据类型的逻辑结构、存储结构和操作实现算法,以及它们在程序中的使用方法。 使学生掌握软件设计的基本内容和设计方法,并培养学生进行规范化软件设计的能力。 3) 使...

    PT80-NEAT开发指南v1.1

    NEAT 开 发 指南 文档 适用于 PT80 系列 移动数据终端 版本记录 版本号 版本描述 发布日期 V 1.0 初始版本。 2012-04-12 V1.1 修改前三章内容 2012-09-25 目录 第一章 关于本手册.....................................

    JavaScript Table行定位效果

    w3c的table部分中说width属性是the desired width of the entire table,我估计entire就是包含了padding和border,找不到什么其他说明,先这么理解吧。 定位方面,除了不支持fixed的ie6用absolute,其他都使用fixed...

    net学习笔记及其他代码应用

    1. 简述 private、 protected...因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。 47.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可...

    亮剑.NET深入体验与实战精要2

    8.1.3 Ajax的工作原理 326 8.1.4 Ajax的优点 326 8.1.5 Ajax的问题 327 8.1.6 Ajax适用场景 327 8.1.7 Ajax不适用场景 329 8.1.8 XMLHttpRequest开发实例 329 8.2 微软VS.NET的Ajax开发 333 8.2.1 安装ASP.NET 2.0 ...

    亮剑.NET深入体验与实战精要3

    8.1.3 Ajax的工作原理 326 8.1.4 Ajax的优点 326 8.1.5 Ajax的问题 327 8.1.6 Ajax适用场景 327 8.1.7 Ajax不适用场景 329 8.1.8 XMLHttpRequest开发实例 329 8.2 微软VS.NET的Ajax开发 333 8.2.1 安装ASP.NET 2.0 ...

Global site tag (gtag.js) - Google Analytics