switch case 跳转表

一、事情来源

  事情来源是一段奇怪的代码,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int x = 1000;
switch (x) {
    case 1000:
    {
        NSLog(@"%d", 1);
    }
    case 2000:
    {
        NSLog(@"%d", 2);
    }
        break;
    case 3:
        NSLog(@"%d", 3);
        break;
    default:
        NSLog(@"%d", -1);
    case 4:
        NSLog(@"%d", 4);
        break;
    case 5:
        NSLog(@"%d", 5);
        break;
}

  当 x = 1000的时候,代码输出的是 1和2 ,也就是 1000 和 2000的case都执行了。(测试环境是Xcode + Mac iphone 模拟器)

  原因是什么?为什么不是和if else if else一样呢

  根据网上的资料,VC6.0的编译器在case数量小于3个的时候,会使用类似if else if else的语句,

  也就是这个case不加break语句的时候,进入下一条case的时候依然需要判断条件。

  当case数量较多的时候,编译器为了优化性能,会产生一个表,表的地址就是case的汇编入口,每个case完了之后下面接着是一个break语句,jump到switch case结束的地方

  如果你的case忘记了break语句,那么很可能继续执行到下一个case,因为所有case的指令都是平铺的,知道运行到break

  那么在iOS的设备上,不管你是几条指令,编译器都会使用跳转表的方式实现。

  比如下面的代码的汇编指令:

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int x = 1;
switch (x) {
    case 1:
    {
        NSLog(@"%d", 1);
    }
    case 2:
    {
        NSLog(@"%d", 2);
    }
    case 3:
        NSLog(@"%d", 3);
 
    case 4:
        NSLog(@"%d", 4);
        break;
    case 5:
        NSLog(@"%d", 5);
        break;
    default:
        NSLog(@"%d", -1);
}

  

对应汇编

 

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
36
37
38
39
40
41
42
43
44
0x10da4361a <+58>:  movl   -0x24(%rbp), %eax
    0x10da4361d <+61>:  decl   %eax
    0x10da4361f <+63>:  movl   %eax, %esi
    0x10da43621 <+65>:  subl   $0x4, %eax
    0x10da43624 <+68>:  movq   %rsi, -0x30(%rbp)
    0x10da43628 <+72>:  movl   %eax, -0x34(%rbp)
    0x10da4362b <+75>:  ja     0x10da436bd               ; <+221> at ViewController.m
    0x10da43631 <+81>:  leaq   0xa4(%rip), %rax          ; -[ViewController viewDidLoad] + 252
    0x10da43638 <+88>:  movq   -0x30(%rbp), %rcx
    0x10da4363c <+92>:  movslq (%rax,%rcx,4), %rdx
    0x10da43640 <+96>:  addq   %rax, %rdx
    0x10da43643 <+99>:  jmpq   *%rdx
    0x10da43645 <+101>: leaq   0x1a14(%rip), %rax        ; @"%d"
    0x10da4364c <+108>: movl   $0x1, %esi
    0x10da43651 <+113>: movq   %rax, %rdi
    0x10da43654 <+116>: movb   $0x0, %al
    0x10da43656 <+118>: callq  0x10da43a14               ; symbol stub for: NSLog
    0x10da4365b <+123>: leaq   0x19fe(%rip), %rax        ; @"%d"
    0x10da43662 <+130>: movl   $0x2, %esi
    0x10da43667 <+135>: movq   %rax, %rdi
    0x10da4366a <+138>: movb   $0x0, %al
    0x10da4366c <+140>: callq  0x10da43a14               ; symbol stub for: NSLog
    0x10da43671 <+145>: leaq   0x19e8(%rip), %rax        ; @"%d"
    0x10da43678 <+152>: movl   $0x3, %esi
    0x10da4367d <+157>: movq   %rax, %rdi
    0x10da43680 <+160>: movb   $0x0, %al
    0x10da43682 <+162>: callq  0x10da43a14               ; symbol stub for: NSLog
    0x10da43687 <+167>: leaq   0x19d2(%rip), %rax        ; @"%d"
    0x10da4368e <+174>: movl   $0x4, %esi
    0x10da43693 <+179>: movq   %rax, %rdi
    0x10da43696 <+182>: movb   $0x0, %al
    0x10da43698 <+184>: callq  0x10da43a14               ; symbol stub for: NSLog
    0x10da4369d <+189>: jmp    0x10da436d3               ; <+243> at ViewController.m:66
    0x10da436a2 <+194>: leaq   0x19b7(%rip), %rax        ; @"%d"
    0x10da436a9 <+201>: movl   $0x5, %esi
    0x10da436ae <+206>: movq   %rax, %rdi
    0x10da436b1 <+209>: movb   $0x0, %al
    0x10da436b3 <+211>: callq  0x10da43a14               ; symbol stub for: NSLog
    0x10da436b8 <+216>: jmp    0x10da436d3               ; <+243> at ViewController.m:66
    0x10da436bd <+221>: leaq   0x199c(%rip), %rax        ; @"%d"
    0x10da436c4 <+228>: movl   $0xffffffff, %esi         ; imm = 0xFFFFFFFF
    0x10da436c9 <+233>: movq   %rax, %rdi
    0x10da436cc <+236>: movb   $0x0, %al
    0x10da436ce <+238>: callq  0x10da43a14               ; symbol stub for: NSLog

  

  可以看到输出1,2,3 的case下面都没jmp指令;case 4的时候,会出现jump指令,也就是上面的代码是采用跳转表进行优化的。

 

  回到最开始的代码,如果如数的x = 30的时候,会输出多少呢?

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int x = 1000;
switch (x) {
    case 1000:
    {
        NSLog(@"%d", 1);
    }
    case 2000:
    {
        NSLog(@"%d", 2);
    }
        break;
    case 3:
        NSLog(@"%d", 3);
        break;
    default:
        NSLog(@"%d", -1);
    case 4:
        NSLog(@"%d", 4);
        break;
    case 5:
        NSLog(@"%d", 5);
        break;
}

  会走到default分支,输出-1;然后走到下面4的case,输出4.

  

  

  

posted @   兜兜有糖的博客  阅读(2735)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示