C语言中switch case语句的实现

在程序中我们经常用到switch case,它的用法就不再用多说了。计算switch中的值然后比较,跳转到相应的分支。很多人说在编译是时通过转化成if。。else来实现的。但实际使用时尤其在调试代码时,每次的case跳转都是直接跳转到匹配值的。这样就与用if。。。else有些矛盾。那么switch。。case究竟如何实现的那?实际试验一下:

   //switch_test1.c

   #include

   #include

   int main()

   {

         int i;

         i=1;

         switch(i)

         {

               case 1:

               printf("%d/n",i);

               break;

               case 2:

               printf("%d/n",i);

               break;

               case 3:

               printf("%d/n",i);

               break;

               case 4:

               printf("%d/n",i);

               break;

               deflaut:

               break;

         }

         return 1;

   } 
 

   test1.cGcc进行到汇编程序的编译

   Gcc  -S test1.c –o test.s

得到文件内容如下:

      .file "switch-test1.c"

      .def ___main; .scl 2; .type 32; .endef

      .section .rdata,"dr"

LC0:

      .ascii "%d/12/0"

      .text

.globl _main

      .def _main; .scl 2; .type 32; .endef

_main:

      pushl %ebp

      movl %esp, %ebp

      subl $24, %esp

      andl $-16, %esp

      movl $0, %eax

      addl $15, %eax

      addl $15, %eax

      shrl $4, %eax

      sall $4, %eax

      movl %eax, -12(%ebp)

      movl -12(%ebp), %eax

      call __alloca

      call ___main

      movl $1, -4(%ebp)

      movl -4(%ebp), %eax

      movl %eax, -8(%ebp)

      cmpl $2, -8(%ebp)

      je L4

      cmpl $2, -8(%ebp)

      jg L8

      cmpl $1, -8(%ebp)

      je L3

      jmp L2

L8:

      cmpl $3, -8(%ebp)

      je L5

      cmpl $4, -8(%ebp)

      je L6

      jmp L2

L3:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L4:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L5:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L6:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

L7:

L2:

      movl $1, %eax

      leave

      ret

      .def _printf; .scl 3; .type 32; .endef 

      Linux的汇编格式与Intel的不同,但是基本指令还是部分相似的,在对操作数取用时会加%,而且源和宿与intel相反。具体的内容不讨论。L3L6程序片段内容基本相似,是调用printf函数实现打印。L8及之前可以看出通过大量的CMPL比较指令进行比较i与立即数比较,相等则实现跳转。查看部分程序:

      cmpl $2, -8(%ebp)#低八位与2比较

      je L4#相等则跳转L4,打印i

      cmpl $2, -8(%ebp) #低八位与2比较

      jg L8//大于则跳转 L8

      cmpl $1, -8(%ebp) #低八位与1比较

      je L3#相等则跳转L3

      jmp L2#跳转到L2,返回1结束程序

L8:

      cmpl $3, -8(%ebp) #低八位与3比较

      je L5#相等则跳转L6,打印i

      cmpl $4, -8(%ebp) #低八位与4比较

      je L6#相等则跳转L6,打印i

      jmp L2 

      经过注释可以看出,所有的case想都经过比较来实现是跳转的。再看一下if-else的实现会如何,

//if_else.c

#include

#include

int main()

{

      int i;

      i=1;

      if(i==1)

      {

            printf("%d/n",i);

      }

      else if(i==2)

      {

            printf("%d/n",i);

      }

      else if(i==3)

      {

            printf("%d/n",i);

      }

      else if(i==4)

      {

            printf("%d/n",i);

      }

      else

      {

      }

      return 1;

} 

同样编译至汇编,查看生成的汇编代码:

      .file "if_else.c"

      .def ___main; .scl 2; .type 32; .endef

      .section .rdata,"dr"

LC0:

      .ascii "%d/12/0"

      .text

.globl _main

      .def _main; .scl 2; .type 32; .endef

_main:

      pushl %ebp

      movl %esp, %ebp

      subl $24, %esp

      andl $-16, %esp

      movl $0, %eax

      addl $15, %eax

      addl $15, %eax

      shrl $4, %eax

      sall $4, %eax

      movl %eax, -8(%ebp)

      movl -8(%ebp), %eax

      call __alloca

      call ___main

      movl $1, -4(%ebp)

      cmpl $1, -4(%ebp)

      jne L2

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L3

L2:

      cmpl $2, -4(%ebp)

      jne L4

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L3

L4:

      cmpl $3, -4(%ebp)

      jne L6

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L3

L6:

      cmpl $4, -4(%ebp)

      jne L3

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

L3:

      movl $1, %eax

      leave

      ret

      .def _printf; .scl 3; .type 32; .endef 

简单阅读就可以发现,两者的程序有点差距,但是基本实现逻辑都是相似,比较i与立即数(c程序中的常数1234),相等时打印,然后跳转到返回结束程序。好像真的是如大家所说,switch-case程序最终实现会使用if-else的逻辑实现。但是为什么,在调试会有直接跳转的问题那,实际程序与例程的区别应该就在,case数上了。实际是case分支高达20多个,那么提升case分支再次尝试一下。

//switch_test2.c

#include

#include

int main()

{

      int i;

      i=1;

      switch(i)

      {

            case 1:

            printf("%d/n",i);

            break;

            case 2:

            printf("%d/n",i);

            break;

            case 3:

            printf("%d/n",i);

            break;

            case 4:

            printf("%d/n",i);

            break;

            case 5:

            printf("%d/n",i);

            break;

            case 6:

            printf("%d/n",i);

            break;

            case 7:

            printf("%d/n",i);

            break;

            case 8:

            printf("%d/n",i);

            break;

            deflaut:

            break;

      }

      return 1;

}

汇编代码:

      .file "switch_test2.c"

      .def ___main; .scl 2; .type 32; .endef

      .section .rdata,"dr"

LC0:

      .ascii "%d/12/0"

      .text

.globl _main

      .def _main; .scl 2; .type 32; .endef

_main:

      pushl %ebp

      movl %esp, %ebp

      subl $24, %esp

      andl $-16, %esp

      movl $0, %eax

      addl $15, %eax

      addl $15, %eax

      shrl $4, %eax

      sall $4, %eax

      movl %eax, -8(%ebp)

      movl -8(%ebp), %eax

      call __alloca

      call ___main

      movl $1, -4(%ebp)

      cmpl $8, -4(%ebp)

      ja L2

      movl -4(%ebp), %eax

      sall $2, %eax

      movl L12(%eax), %eax

      jmp *%eax

      .section .rdata,"dr"

      .align 4

L12:

      .long L2

      .long L3

      .long L4

      .long L5

      .long L6

      .long L7

      .long L8

      .long L9

      .long L10

      .text

L3:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L4:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L5:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L6:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L7:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L8:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L9:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

      jmp L2

L10:

      movl -4(%ebp), %eax

      movl %eax, 4(%esp)

      movl $LC0, (%esp)

      call _printf

L11:

L2:

      movl $1, %eax

      leave

      ret

      .def _printf; .scl 3; .type 32; .endef

简单的分析可以看出L12作为一个表项,实现了到L2L10的跳转。那么可以看出,switch-case在实现时当case项比较多时,会通过生成查询表来提高程序的效率,空间换时间。

      在此基础上,对case的顺序打乱,只要case的值比较规律(数据差相同),都是汇编成查询表,但是各个case值之间非常离散时,即无规律可言时,是不能能生成查询表的,只能使用if-else的方式。这种情况下只用将最有可能的值放在第一个比较判断的位置才能极大提高程序效率。

你可能感兴趣的:(C语言中switch case语句的实现)