首先看一到用 c 编写的程序
/* -------------------- filename : ta.c --------------- */
int switch_test_first( int x )
{
        int res ;
        switch( x ){
                case 100 :
                        res = 1 ;
                        break ;
                case 102 :
                        res = 2 ;
                        break ;
                case 103 :
                        res = 3 ;
                        break ;
        }
        return res ;
}
然后,我们用 gcc 将它编译成汇编文件( 使用 -S 开关 )
gcc -S ta.c
将得到如下的汇编文件( ta.s )
        .file   "ta.c"
        .text
.globl switch_test_first
        .type   switch_test_first,@function
switch_test_first:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        .file   "ta.c"
        .text
.globl switch_test_first
        .type   switch_test_first,@function
switch_test_first:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        movl    %eax, -8(%ebp)
        cmpl    $102, -8(%ebp)          // 1
        je      .L4                     // 2
        cmpl    $102, -8(%ebp)          // 3   
        jg      .L8                     // 4
        cmpl    $100, -8(%ebp)          // 5
        je      .L3                     // 6
        jmp     .L2                     // 7
.L8:
        cmpl    $103, -8(%ebp)
        je      .L5
        jmp     .L2
.L3:
        movl    $1, -4(%ebp)
        jmp     .L2
.L4:
        movl    $2, -4(%ebp)
        jmp     .L2
.L5:
        movl    $3, -4(%ebp)
.L2:
        movl    -4(%ebp), %eax
        leave
        ret
.Lfe1:
        .size   switch_test_first,.Lfe1-switch_test_first
        .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

注意看文件中 // 1 ~ // 7 的部份,从这个部份,我们可以看出,gcc确实是把一些case语句转成了李维所说的那种方式进行处理,我们看见了代码中存在有众多的 cmpl 与 jmp 语句
这就相当于你使用if..else..一样,但是否总是这样呢?

我们下面改动一下 ta.c 这个文件,在里面再多加一些 case 语句
/* -------------- filename : new_ta.c ------------------- */
int switch_test_first( int x )
{
        int res ;
        switch( x ){
                case 100 :
                        res = 1 ;
                        break ;
                case 102 :
                        res = 2 ;
                        break ;
                case 103 :
                        res = 3 ;
                        break ;
                case 104 :
                        res = 4 ;
                        break ;
                case 105 :
                        res = 5 ;
                        break ;
                case 106 :
                        res = 6 ;
                        break ;
        }
        return res ;
}
这个 new_ta.c 与原来的 ta.c 在结构上完全相同,唯一不同的就是 case 语句的数量变多了,下面我们来编译一下这个文件
gcc -S new_ta.c
下面是我们产生的更新的汇编文件
        .file   "new_ta.c"
        .text
.globl switch_test_first
        .type   switch_test_first,@function
switch_test_first:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        subl    $100, %eax
        movl    %eax, -8(%ebp)
        cmpl    $6, -8(%ebp)
        ja      .L2
        movl    -8(%ebp), %edx
        movl    .L9(,%edx,4), %eax
        jmp     *%eax
        .section        .rodata
        .align 4
        .align 4
.L9:                             // A
        .long   .L3
        .long   .L2
        .long   .L4
        .long   .L5
        .long   .L6
        .long   .L7
        .long   .L8
        .text
.L3:                            // 1
        movl    $1, -4(%ebp)
        jmp     .L2
.L4:                            // 2
        movl    $2, -4(%ebp)
        jmp     .L2
.L5:                            // 3
        movl    $3, -4(%ebp)
        jmp     .L2             // 4  
.L6:
        movl    $4, -4(%ebp)
        jmp     .L2             // 5
.L7:
        movl    $5, -4(%ebp)    // 6
        jmp     .L2
.L8:                            // 7
        movl    $6, -4(%ebp)
.L2:                           
        movl    -4(%ebp), %eax
        leave
        ret
.Lfe1:
        .size   switch_test_first,.Lfe1-switch_test_first
        .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

仔细比较一下这个最新的 new_ta.s 与前面的 ta.s,精华全在里面了!
首先 new_ta.s 比前面的 ta.s 多了一个 .L9 部分,而且它的 // 1 ~ // 7 中没有了前面
ta.s 文件中所存在的众多的 cmpl 与 jmp 语句,那么,现在这样的代码又是怎么实现
switch 语句中的跳转的呢?我们来仔细分析一下它新多出来的 .L9 部份。
        .section        .rodata
        .align 4
        .align 4
.L9:
        .long   .L3
        .long   .L2
        .long   .L4
        .long   .L5
        .long   .L6
        .long   .L7
        .long   .L8
        .text
显而易见,.L9 部份是一个我们最常见的数据结构——表,它的每一项都是一个标号,而这个标号,恰恰是每个 case 语句的入口标号!
这很容易让我们想到,它很可能是用了一张表来存放所有的 case 语句的入口,然后,在
执行 switch 语句的时候就从这个表中直接检出相应的 case 语句的入口地址,然后跳转
到相应的 case 语句去执行,就像hash_table似的。具体是不是这样呢?我们看看进入
switch 部份的代码:

pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        movl    8(%ebp), %eax
        subl    $100, %eax
        movl    %eax, -8(%ebp)
        cmpl    $6, -8(%ebp)
        ja      .L2
        movl    -8(%ebp), %edx
        movl    .L9(,%edx,4), %eax // 1
        jmp     *%eax              // 2
果然如此!首先在 // 1 处根据%edp的值(其值相当于表的下标)在.L9的表中找到相应
case 语句的入口地址,并把这个地址存到%eax中,然后通过 // 2 (这是一个间接跳转
语句)转到%eax存放的地址中,也即相应的case语句处。
C编译器,果然聪明!

通过这个分析我们可以知道如下两点:
1. 当 case 语句少的时候,C编译器将其转成 if..else.. 类型进行处理,运用较多的
   cmp 与 jmp 语句 ,而当 case 语句较多的时候,C编译器会出成一个跳转表,而直
   接通过跳转表进行跳转,这让 switch 具有非常高的效律,而且效律几乎不会因为
   case 语句的增长而减小,李维所担忧的问题是完全不会发生的
2. 可以问答下面几个问题:
   1. 为什么 case 语句中需要的是整数类型而不能是其余的类型?
      这是因为,case 语句中的这个值是用来做跳转表的下标的,因此,当然必须是整数
   2. 为什么 case 语句在不加break的时候具有直通性?
      这是因为跳转是在进入 switch 是计算出的,而不是在case语句中计算出的,整个
      case 语句群就是一块完整而连续的代码,只是switch让其从不同的位置开始执行。

上面的内容,在《Computer Systems A Programmer's Perspective》中有很详细的论述,
感兴趣可以去找来仔细看看~~~

既然,case 语句需要的是整数的常量值,那么我们是否可用 const 类型呢?比如下面
一段代码:

const int c_1 = 100 ;
const int c_2 = 102 ;

void test( int x )
{
        switch( x ){
                case c_1 :
                        ++x ;
                case c_2 :
                        --x ;
        }
}

这段代码,用 c 编译器编译,编译器会提示错误,但在 c++ 编译器中却不会,这主要是由于 c , 与 c++ 编译器对 const 这个东东的处理不同。我们来看看下面一段 c 程序
/*------------- filename : const_c.c -----------*/
const int a = 15 ;

void f( int x )
{
        x = a ;
}
同样用 gcc 编译
gcc -S const_c.c
然后,来看看它的汇编文件
        .file   "const_c.c"
.globl a
        .section        .rodata
        .align 4
        .type   a,@object
        .size   a,4
a:                             // 1
        .long   15
        .text
.globl f
        .type   f,@function
f:
        pushl   %ebp
        movl    %esp, %ebp
        movl    a, %eax        // 2 
        movl    %eax, 8(%ebp)
        leave
        ret
.Lfe1:
        .size   f,.Lfe1-f
        .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
注意 // 1 处,C 编译器为 a 分配了地址,并把它的值设为 15 ,而在 // 2 处,它是将
a 这个地址中的值赋给了 %eax,这同一般的普通变量而非const 变量赋值没什么两样

下面我们用 c++ 编译器来编译这段代码,它产生的汇编文件如下:
        .file   "const_cpp.cpp"
        .text
        .align 2
.globl _Z1fi
        .type   _Z1fi,@function
_Z1fi:
.LFB2:
        pushl   %ebp
.LCFI0:
        movl    %esp, %ebp
.LCFI1:
        movl    $15, 8(%ebp)  // 1
        leave
        ret
.LFE2:
.Lfe1:
        .size   _Z1fi,.Lfe1-_Z1fi
        .section        .rodata
        .align 4
        .type   a,@object
        .size   a,4
a:
        .long   15
        .ident  "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
同样注意// 1 处,它以经把 a 的值用 15 来取代了,
也就是说,在c中const变量的行为更像一个非const变量,而在cpp中,const变量的行为就像是#define
由于 c++ 中,const 变量的值是在编译时就计算出来的,因此,它可以用在 case 语句中,而 c 中,const值在编译时只是一个变量的地址,因此,它无法用在 case 语句中.

更深入一点理解switch语句及c/c++对const的处理的更多相关文章

  1. 【JS】332- 为什么我更喜欢对象而不是 switch 语句

    昨天偷懒了,欢迎点击关注???这样我就多更大的动力日更了- 正文从这里开始~~~ 最近(或者不是最近,这完全取决于您什么时候阅读这边文章),我正在跟我的团队伙伴讨论如何去处理这种需要根据不同的值去处理 ...

  2. C#中一种替换switch语句更优雅的写法

    今天在项目中遇到了使用switch语句判断条件,但问题是条件比较多,大概有几十个条件,满屏幕的case判断,是否有更优雅的写法替代switch语句呢? 假设有这样的一个场景:商场经常会根据情况采取不同 ...

  3. JS流程控制语句 多种选择(Switch语句) 当有很多种选项的时候,switch比if else使用更方便。

    多种选择(Switch语句) 当有很多种选项的时候,switch比if else使用更方便. 语法: switch(表达式) { case值1: 执行代码块 1 break; case值2: 执行代码 ...

  4. 通过goto语句学习if...else、switch语句并简单优化

    goto语句在C语言中实现的就是无条件跳转,第二章一上来就介绍goto语句就是要通过goto语句来更加清楚直观的了解控制结构. 我理解的goto语句其实跟switch语句有相似之处,都是进行跳转.不同 ...

  5. android官方技术文档翻译——switch 语句转换

    本文译自androd官方技术文档<Switch Statement Conversion>,原文地址:http://tools.android.com/tips/non-constant- ...

  6. 透过IL看C#:switch语句(转)

    透过IL看C# switch语句(上) 摘要: switch语句是 C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码.本文介绍了当向 switch语句中传入不同类型的参数时,编译器为其生 ...

  7. 关于css中层叠性的一点理解

    关于css层叠性的一点理解 标签(空格分隔): html css 我们平时在写css的时候会遇到这样的情况 <!DOCTYPE html> <html lang="en&q ...

  8. 通过字节码分析java中的switch语句

    在一次做题中遇到了switch的问题,由于对switch执行顺序的不了解,在这里简单的通过字节码的方式理解一下switch执行顺序(题目如下): public class Ag{ static pub ...

  9. switch语句(下)(转载)

    之前我们介绍了在switch语句中使用整数类型和枚举类型的情况.这一部分继续介绍使用string类型的情况.string类型是switch语句接受的唯一一种引用类型参数. 下面来看一段C#代码. 代码 ...

随机推荐

  1. Light OJ - 1058 Parallelogram Counting(判定平行四边形)

    Description There are n distinct points in the plane, given by their integer coordinates. Find the n ...

  2. hdu_5085_Counting problem(莫队分块思想)

    题目连接:hdu_5085_Counting problem 题意:给你一个计算公式,然后给你一个区间,问这个区间内满足条件的数有多少个 题解:由于这个公式比较特殊,具有可加性,我们考虑讲一个数分为两 ...

  3. 解读QML之四

    解读QML之四 QML对象属性 每一个QML对象类型都定义了一系列属性.每创建一个该对象类型的实例,该实例的这些属性也自动被创建了.接下来我们讨论几种不同类型的属性. id属性 每一个QML对象类型都 ...

  4. zf-关于邵阳市打印模块个别单号打印之后不会跳转到收费模块的BUG的解决方法

    原因是 办结的时候 有个收费管理,里面会生成收费项目的单号,但是有1个单号是有问题的,没有关联到数据库里面的其他的表,所以打印之后不能跳转.如果跳转到收费模块 那么数据库里面的一个flag字段会变成9 ...

  5. 洛谷U4727 小L 的二叉树

    U4727 小L 的二叉树 题目背景 勤奋又善于思考的小L接触了信息学竞赛,开始的学习十分顺利.但是,小L对数据结构的掌握实在十分渣渣. 所以,小L当时卡在了二叉树. 题目描述 在计算机科学中,二叉树 ...

  6. android脚步---APP引导页添加

    package com.leadcore.uudatoutie; import java.util.ArrayList; import com.leadcore.uudatoutie.R; impor ...

  7. CentOS6.5 安装snort

    本机CentOS6.5最大化安装,以下安装所需组件也是最大化安装之后仍需自己安装的. 1.安装libpcap与libpcap-devel yum -y install libpcap* 2.安装lib ...

  8. C​# ​日​期​时​间

    //获取日期+时间 DateTime.Now.ToString(); // 2008-9-4 20:02:10 DateTime.Now.ToLocalTime().ToString(); // 20 ...

  9. JS读RSS

    <html>  <head>   <title>javascript读取RSS数据</title>   <META content="t ...

  10. 004-Python字符串

    Python 字符串(str) 字符串是 Python 中最常用的数据类型.我们可以使用引号('或")来创建字符串.创建字符串很简单,只要为变量分配一个值即可. var1 = "H ...