关于switch的讨论
无意在网上看到一篇关于if与switch的问题帖子,其中题主提问了关于为什么同一种逻辑实现, 用if写和用switch写得到的结果却不同。
关于这点我一开始是不信的,代码写旧了总会自己预设一些假设,同时一些已经实现的功能或代码也总是抱着100%信任的态度,但是这往往就造成了许多意想不到的bug的出现。因此本着实事求是的原则,还是做了这么个实验。先用if来实现一个分支选择的功能。
1 2 3 4 5 6 7 8 9 10 11 12
| int main(int argc, char **argv) { int i = 1,j = 0; if (i == 1) j = 1; else if (i == 2) j = 2; else if (i == 3) j = 3; else j = 4; }
|
编译这段代码(编译器Apple clang version 11.0.0 (clang-1100.0.33.8);Target: x86_64-apple-darwin19.0.0),得到如下汇编指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 0000000000000000 <_main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) b: 89 7d f8 mov %edi,-0x8(%rbp) e: 48 89 75 f0 mov %rsi,-0x10(%rbp) 12: c7 45 ec 01 00 00 00 movl $0x1,-0x14(%rbp) 19: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%rbp) 20: 83 7d ec 01 cmpl $0x1,-0x14(%rbp) 24: 0f 85 0c 00 00 00 jne 36 <_main+0x36> 2a: c7 45 e8 01 00 00 00 movl $0x1,-0x18(%rbp) 31: e9 3d 00 00 00 jmpq 73 <_main+0x73> 36: 83 7d ec 02 cmpl $0x2,-0x14(%rbp) 3a: 0f 85 0c 00 00 00 jne 4c <_main+0x4c> 40: c7 45 e8 02 00 00 00 movl $0x2,-0x18(%rbp) 47: e9 22 00 00 00 jmpq 6e <_main+0x6e> 4c: 83 7d ec 03 cmpl $0x3,-0x14(%rbp) 50: 0f 85 0c 00 00 00 jne 62 <_main+0x62> 56: c7 45 e8 03 00 00 00 movl $0x3,-0x18(%rbp) 5d: e9 07 00 00 00 jmpq 69 <_main+0x69> 62: c7 45 e8 04 00 00 00 movl $0x4,-0x18(%rbp) 69: e9 00 00 00 00 jmpq 6e <_main+0x6e> 6e: e9 00 00 00 00 jmpq 73 <_main+0x73> 73: 8b 45 fc mov -0x4(%rbp),%eax 76: 5d pop %rbp 77: c3 retq
|
从总可以很容易看出if指令在汇编级是由cmpl与jne组合实现的。
那么再看switch又有什么区别呢?如下为用switch实现的分支选择。
1 2 3 4 5 6 7 8 9 10
| int main(int argc, char **argv) { int i = 1,j = 0; switch (i) { case 1: j = 1; break; case 2: j = 2; break; case 3: j = 3; break; default: j = 4; break; } }
|
switch实现与if实现逻辑功能完全相同,但汇编级实现却不一样,其反汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 0000000000000000 <_main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) b: 89 7d f8 mov %edi,-0x8(%rbp) e: 48 89 75 f0 mov %rsi,-0x10(%rbp) 12: c7 45 ec 01 00 00 00 movl $0x1,-0x14(%rbp) 19: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%rbp) 20: 8b 7d ec mov -0x14(%rbp),%edi 23: 89 f8 mov %edi,%eax 25: 83 e8 01 sub $0x1,%eax 28: 89 7d e4 mov %edi,-0x1c(%rbp) 2b: 89 45 e0 mov %eax,-0x20(%rbp) 2e: 0f 84 2d 00 00 00 je 61 <_main+0x61> 34: e9 00 00 00 00 jmpq 39 <_main+0x39> 39: 8b 45 e4 mov -0x1c(%rbp),%eax 3c: 83 e8 02 sub $0x2,%eax 3f: 89 45 dc mov %eax,-0x24(%rbp) 42: 0f 84 25 00 00 00 je 6d <_main+0x6d> 48: e9 00 00 00 00 jmpq 4d <_main+0x4d> 4d: 8b 45 e4 mov -0x1c(%rbp),%eax 50: 83 e8 03 sub $0x3,%eax 53: 89 45 d8 mov %eax,-0x28(%rbp) 56: 0f 84 1d 00 00 00 je 79 <_main+0x79> 5c: e9 24 00 00 00 jmpq 85 <_main+0x85> 61: c7 45 e8 01 00 00 00 movl $0x1,-0x18(%rbp) 68: e9 1f 00 00 00 jmpq 8c <_main+0x8c> 6d: c7 45 e8 02 00 00 00 movl $0x2,-0x18(%rbp) 74: e9 13 00 00 00 jmpq 8c <_main+0x8c> 79: c7 45 e8 03 00 00 00 movl $0x3,-0x18(%rbp) 80: e9 07 00 00 00 jmpq 8c <_main+0x8c> 85: c7 45 e8 04 00 00 00 movl $0x4,-0x18(%rbp) 8c: 8b 45 fc mov -0x4(%rbp),%eax 8f: 5d pop %rbp 90: c3 retq
|
从反汇编结果来看,20-5c部分为switch的条件码计算、比较和跳转的逻辑,而61-85则是case中包含的执行语句。
通过比较这两种实现的汇编代码,可以得到if与switch的几点重大区别。
- if的条件码比较和分支过程是紧临的,switch的条件码比较和分支执行过程是松散的。
- if的条件码比较和跳转使用cmpl指令和jne指令,switch的条件码比较与跳转使用的是sub,je指令
- if的比较过程不改变源操作数的结果仅改变状态寄存器的值,switch的比较过程会改变源操作数的结果和状态寄存器的值,因此需要保存源操作数,从以上汇编代码可知每次都会将比较的原值与计算的结果存入堆栈。
对于if与switch在汇编级的区别,结果是显然的了,但是从逻辑上来看它们的实现都是正确的,因此在这里没能看出上面帖子所出现的问题的根本原因,但是从上面分析中知道,如果在堆栈容量不足的情况下,if的实现绝对是优先的选择,因为switch的实现需要占用更多的堆栈空间,在堆栈容量不足时确实存在堆栈溢出的可能,从而造成内存相关的错误甚至一些未知错误。
同时在内核中也有很多地方选择if而不选择switch,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| /* Can't use a switch () here: gcc isn't always smart enough for that... */ if ((i) == -16) IA64_FETCHADD(_tmp, _v, -16, sizeof(*(v)), sem); else if ((i) == -8) IA64_FETCHADD(_tmp, _v, -8, sizeof(*(v)), sem); else if ((i) == -4) IA64_FETCHADD(_tmp, _v, -4, sizeof(*(v)), sem); else if ((i) == -1) IA64_FETCHADD(_tmp, _v, -1, sizeof(*(v)), sem); else if ((i) == 1) IA64_FETCHADD(_tmp, _v, 1, sizeof(*(v)), sem); else if ((i) == 4) IA64_FETCHADD(_tmp, _v, 4, sizeof(*(v)), sem); else if ((i) == 8) IA64_FETCHADD(_tmp, _v, 8, sizeof(*(v)), sem); else if ((i) == 16) IA64_FETCHADD(_tmp, _v, 16, sizeof(*(v)), sem); else \ _tmp = __bad_increment_for_ia64_fetch_and_add(); (__typeof__(*(v))) (_tmp); /* return old value */
|
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com