在C语言中,教科书告诉我们switch...case...语句比if...else if...else执行效率要高,但这到底是为什么呢?本文尝试从汇编的角度予以分析并揭晓其中的奥秘。

第一步,写一个demo程序:foo.c

 #include <stdio.h>

 static int
foo_ifelse(char c)
{
if (c == '' || c == '') {
c += ;
} else if (c == 'a' || c == 'b') {
c += ;
} else if (c == 'A' || c == 'B') {
c += ;
} else {
c += ;
} return (c);
} static int
foo_switch(char c)
{
switch (c) {
case '':
case '': c += ; break;
case 'b':
case 'a': c += ; break;
case 'B':
case 'A': c += ; break;
default: c += ; break;
} return (c);
} int
main(int argc, char **argv)
{
int m1 = foo_ifelse('');
int m2 = foo_ifelse('');
int n1 = foo_switch('a');
int n2 = foo_switch('b');
(void) printf("%c %c %c %c\n", m1, m2, n1, n2);
return ();
}

第二步,在Ubuntu上使用gcc编译

$ gcc -g -o foo foo.c

第三步,使用gdb对二进制文件foo反汇编 (使用intel语法)

o 反汇编foo_ifelse()

 (gdb) set disassembly-flavor intel
(gdb) disas /m foo_ifelse
Dump of assembler code for function foo_ifelse:
{
0x0804841d <+>: push ebp
0x0804841e <+>: mov ebp,esp
0x08048420 <+>: sub esp,0x4
0x08048423 <+>: mov eax,DWORD PTR [ebp+0x8]
0x08048426 <+>: mov BYTE PTR [ebp-0x4],al if (c == '' || c == '') {
0x08048429 <+>: cmp BYTE PTR [ebp-0x4],0x30
0x0804842d <+>: je 0x8048435 <foo_ifelse+>
0x0804842f <+>: cmp BYTE PTR [ebp-0x4],0x31
0x08048433 <+>: jne 0x8048441 <foo_ifelse+> c += ;
0x08048435 <+>: movzx eax,BYTE PTR [ebp-0x4]
0x08048439 <+>: add eax,0x1
0x0804843c <+>: mov BYTE PTR [ebp-0x4],al
0x0804843f <+>: jmp 0x804847b <foo_ifelse+> } else if (c == 'a' || c == 'b') {
0x08048441 <+>: cmp BYTE PTR [ebp-0x4],0x61
0x08048445 <+>: je 0x804844d <foo_ifelse+>
0x08048447 <+>: cmp BYTE PTR [ebp-0x4],0x62
0x0804844b <+>: jne 0x8048459 <foo_ifelse+> c += ;
0x0804844d <+>: movzx eax,BYTE PTR [ebp-0x4]
0x08048451 <+>: add eax,0x2
0x08048454 <+>: mov BYTE PTR [ebp-0x4],al
0x08048457 <+>: jmp 0x804847b <foo_ifelse+> } else if (c == 'A' || c == 'B') {
0x08048459 <+>: cmp BYTE PTR [ebp-0x4],0x41
0x0804845d <+>: je 0x8048465 <foo_ifelse+>
0x0804845f <+>: cmp BYTE PTR [ebp-0x4],0x42
0x08048463 <+>: jne 0x8048471 <foo_ifelse+> c += ;
0x08048465 <+>: movzx eax,BYTE PTR [ebp-0x4]
0x08048469 <+>: add eax,0x3
0x0804846c <+>: mov BYTE PTR [ebp-0x4],al
0x0804846f <+>: jmp 0x804847b <foo_ifelse+> } else {
c += ;
0x08048471 <+>: movzx eax,BYTE PTR [ebp-0x4]
0x08048475 <+>: add eax,0x4
0x08048478 <+>: mov BYTE PTR [ebp-0x4],al } return (c);
0x0804847b <+>: movsx eax,BYTE PTR [ebp-0x4] }
0x0804847f <+>: leave
0x08048480 <+>: ret End of assembler dump.
(gdb)

o 反汇编foo_switch()

 (gdb) set disassembly-flavor intel
(gdb) disas /m foo_switch
Dump of assembler code for function foo_switch:
{
0x08048481 <+>: push ebp
0x08048482 <+>: mov ebp,esp
0x08048484 <+>: sub esp,0x4
0x08048487 <+>: mov eax,DWORD PTR [ebp+0x8]
0x0804848a <+>: mov BYTE PTR [ebp-0x4],al switch (c) {
0x0804848d <+>: movsx eax,BYTE PTR [ebp-0x4]
0x08048491 <+>: sub eax,0x30
0x08048494 <+>: cmp eax,0x32
0x08048497 <+>: ja 0x80484c6 <foo_switch+>
0x08048499 <+>: mov eax,DWORD PTR [eax*+0x80485f0]
0x080484a0 <+>: jmp eax case '':
case '': c += ; break;
0x080484a2 <+>: movzx eax,BYTE PTR [ebp-0x4]
0x080484a6 <+>: add eax,0x1
0x080484a9 <+>: mov BYTE PTR [ebp-0x4],al
0x080484ac <+>: jmp 0x80484d1 <foo_switch+> case 'b':
case 'a': c += ; break;
0x080484ae <+>: movzx eax,BYTE PTR [ebp-0x4]
0x080484b2 <+>: add eax,0x2
0x080484b5 <+>: mov BYTE PTR [ebp-0x4],al
0x080484b8 <+>: jmp 0x80484d1 <foo_switch+> case 'B':
case 'A': c += ; break;
0x080484ba <+>: movzx eax,BYTE PTR [ebp-0x4]
0x080484be <+>: add eax,0x3
0x080484c1 <+>: mov BYTE PTR [ebp-0x4],al
0x080484c4 <+>: jmp 0x80484d1 <foo_switch+> default: c += ; break;
0x080484c6 <+>: movzx eax,BYTE PTR [ebp-0x4]
0x080484ca <+>: add eax,0x4
0x080484cd <+>: mov BYTE PTR [ebp-0x4],al
0x080484d0 <+>: nop } return (c);
0x080484d1 <+>: movsx eax,BYTE PTR [ebp-0x4] }
0x080484d5 <+>: leave
0x080484d6 <+>: ret End of assembler dump.
(gdb)

分析:
(1)在foo_ifelse()中,采用的方法是按顺序比较,如满足条件,则执行对应的代码,否则跳转到下一个分支再进行比较;

(2)在foo_switch()中,下面的这段汇编代码比较有意思,

..
switch (c) {
0x0804848d <+>: movsx eax,BYTE PTR [ebp-0x4]
0x08048491 <+>: sub eax,0x30
0x08048494 <+>: cmp eax,0x32
0x08048497 <+>: ja 0x80484c6 <foo_switch+>
0x08048499 <+>: mov eax,DWORD PTR [eax*+0x80485f0]
0x080484a0 <+>: jmp eax
..

注意: 第17行 jmp eax

也就是说,当c的取值不同,是什么机制保证第17行能跳转到正确的位置开始执行呢?

第16行: eax = [eax * 4 + 0x80485f0]

搞清楚了从地址0x80485f0开始,对应的内存里面的内容也就回答了刚才的问题。

执行完第16行后,

当c为'1'或'0'时, eax的值应该是0x080484a2;

当c为'b'或'a'时, eax的值应该是0x080484ae;

当c为'B'或'A'时, eax的值应该是0x080484ba;

通过gdb查看对应的内存,确实如此!

>>> ord('') - 0x30

>>> ord('') - 0x30

(gdb) x /2wx  *+0x80485f0
0x80485f0: 0x080484a2 0x080484a2 >>> ord('b') - 0x30 >>> ord('a') - 0x30 (gdb) x /2wx *+0x80485f0
0x80486b4: 0x080484ae 0x080484ae >>> ord('B') - 0x30 >>> ord('A') - 0x30 (gdb) x /2wx *+0x80485f0
0x8048634: 0x080484ba 0x080484ba

那么,我们可以大胆的猜测,虽然c的取值不同但是跳转的IP确实是精准无误的,一定是编译阶段就被设定好了,果真如此吗? 接下来分析一下对应的二进制文件foo,

第四步,使用objdump查看foo,

$ objdump -D foo > /tmp/x

$ vim /tmp/x
509 Disassembly of section .rodata:
...
518 80485f0: a2 84 04 08 a2 mov %al,0xa2080484
519 80485f5: 84 04 08 test %al,(%eax,%ecx,1)
...
534 8048630: c6 84 04 08 ba 84 04 movb $0x8,0x484ba08(%esp,%eax,1)
535 8048637:
536 8048638: ba 84 04 08 c6 mov $0xc6080484,%edx
...
566 80486b0: c6 84 04 08 ae 84 04 movb $0x8,0x484ae08(%esp,%eax,1)
567 80486b7:
568 80486b8: ae scas %es:(%edi),%al
569 80486b9: 84 04 08 test %al,(%eax,%ecx,1)
...

在0x80485f0地址,存的8个字节正好是0x080484a2, 0x080484a2 (注意:按照小端的方式阅读)

在0x80486b4地址,存的8个字节正好是0x080484ae, 0x080484ae

在0x8048634地址,存的8个字节正好是0x080484ba,0x080484ba

果然不出所料,要跳转的IP的值正是在编译的时候存入了.rodata(只读数据区)。一旦foo开始运行,对应的内存地址就填写上了正确的待跳转地址,接下来只不过是根据c的取值计算出对应的IP存放的内存起始地址X,从X中取出待跳转的地址,直接跳转就好。

    0x08048499 <+>:    mov    eax,DWORD PTR [eax*+0x80485f0]
0x080484a0 <+>: jmp eax

到此为止,我们已经搞清楚了为什么switch...case...语句相对于if...else if...else...来说执行效率要高的根本原因。简言之,编译的时候创建了一个map存于.rodata区中,运行的时候直接根据输入(c的值)查表,找到对应的IP后直接跳转。 (省去了cmp, jmp -> cmp, jmp -> cmp, jmp...这一冗长的计算过程。)

总结: switch...case...执行效率高,属于典型的以空间换时间。也就是说,(套用算法的行话)以提高空间复杂度为代价降低了时间复杂度。

为什么switch...case语句比if...else执行效率高的更多相关文章

  1. 为什么说在使用多条件判断时switch case语句比if语句效率高?

    在学习JavaScript中的if控制语句和switch控制语句的时候,提到了使用多条件判断时switch case语句比if语句效率高,但是身为小白的我并没有在代码中看出有什么不同.去度娘找了半个小 ...

  2. java中的Switch case语句

    java中的Switch case 语句 在Switch语句中有4个关键字:switch,case break,default. 在switch(变量),变量只能是整型或者字符型,程序先读出这个变量的 ...

  3. switch… case 语句的用法

    switch… case 语句的用法   public class Test7 { public static void main(String[] args) { int i=5; switch(i ...

  4. if语句,if...else if语句和switch...case语句的区别和分析

    前段时间在工作中遇到了一个关于条件判断语句的问题,在if语句,if else if语句和switch case语句这三者之间分析,使用其中最有效率的一种方法. 所以就将这个问题作为自己第一篇博客的主要 ...

  5. JavaScript基础知识(if、if else、else if、while、switch...case语句)

    13.语句 概念:就是分号(:) 代表一条语句的结束 习惯:一行只编写一条语句:一行编写多条语句(代码可读性较差) 语句块:可以包含多条语句     "{ }"将多条语句包裹 u ...

  6. Java基础之循环语句、条件语句、switch case 语句

    Java 循环结构 - for, while 及 do...while 顺序结构的程序语句只能被执行一次.如果您想要同样的操作执行多次,,就需要使用循环结构. Java中有三种主要的循环结构: whi ...

  7. JavaSE基础(七)--Java流程控制语句之switch case 语句

    Java switch case 语句 switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支. 语法 switch case 语句语法格式如下: switch(exp ...

  8. 逆向知识第九讲,switch case语句在汇编中表达的方式

    一丶Switch Case语句在汇编中的第一种表达方式 (引导性跳转表) 第一种表达方式生成条件: case 个数偏少,那么汇编中将会生成引导性的跳转表,会做出 if else的情况(类似,但还是能分 ...

  9. Java switch case 语句

    switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支. 语法 switch(expression){ case value : //语句 break; //可选 ca ...

随机推荐

  1. HttpClient的使用-爬虫学习(一)

    Apache真是伟大,为我们提供了HttpClient.jar,这个HttpClient是客户端的http通信实现库,这个类库的作用是接受和发送http报文,引进这个类库,我们对于http的操作会变得 ...

  2. FineUI开源版之TreeGrid(修改)

    上篇文章中做了简单实现,但是还是有bug的,还需要在外面写事件的处理,今天又进行修改了. 下面放出代码,同样的  hzh modify标记的就是我进行修改的地方 grid.cs 添加代码 #regio ...

  3. tornado\ioloop.py单例

    @staticmethod def instance(): """Returns a global `IOLoop` instance. Most application ...

  4. cocos2d-x-2.2.0_win7+vs2010

    cocos2d-x-2.2.0_win7+vs2010搭建_eclipse+ndk-r9+cygwin搭建_教程以及编译问题汇总 声明:我是才用c/c++和cocos2d-x的如果有错误欢迎指出 文章 ...

  5. 马丁·福勒-page对象

    马丁·福勒-page对象 译者注:这篇文章翻译自马丁·福勒(Martin Flower,对,没错,就是软件教父)官网的一篇文章,原文出处在文底.如果你正在做WEB自动化测试,那么我强烈推荐你看这篇文章 ...

  6. 简单的mvc之一:简单的开始

    mvc学习到现在,相对所学到的一系列的知识做一个总结,于是就有了这个标题—简单的mvc.文如名,写的是简单的mvc的知识,目标群也不言而喻.这一篇来个简单的开始,从头建立一个web项目,比如hello ...

  7. js在IE浏览器和非IE浏览器中的兼容性问题

    下面列出IE和非IE中常见的一些js兼容性问题.  //window.event   IE:有window.event对象   非IE:没有window.event对象.可以通过给函数的参数传递eve ...

  8. LigerUI权限系统之用户管理

    用户管理较之前的的组织结构和菜单管理稍显复杂.不管怎样还是先上图吧,再来讲解 左边是组织结构,右边是用户,用户是跟组织机构挂钩的,通过点击左边的组织结构,来刷新右边,加载该组织机构下的用户. 用户管理 ...

  9. 【Oracle】-【体系结构】-【DBWR】-DBWR进程相关理解

    对DBWR的一些理解 首先从名称上,DBWR全称是Database Writer Process,属于Oracle后台进程的一种,有的地方也叫DBWn,我想这里是出于DBWR进程个数的原因,DBWR进 ...

  10. Servle中的会话管理

    最近整理了下会话管理的相关笔记,以下做个总结: 一.会话管理(HttpSession) 1.Web服务器跟踪客户状态的四种方法: 1).使用Servlet API的Session机制(常用) 2).使 ...