我是靠谱客的博主 粗暴宝马,最近开发中收集的这篇文章主要介绍C语言的 switch 语句的底层汇编具体实现(以ARM汇编为例讲解)一、参考资料二、具体分析总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

我于昨晚去世,走时心如止水。我于今早重生,来时心怀暖阳。敬你岁月无波澜,祝我余生不悲欢!
散文集 - 《我在人间凑数的日子》

一、参考资料

ARM M3/M4汇编指令TBB TBH实现复杂表格跳转

C语言switch语句的汇编语言实现

C语言汇编代码分析(switch case)

本文是在看大佬们的文章后自己总结的,感谢大佬们的文章分享。

二、具体分析

我们知道 C 语言的 switch 关键字可以让一些情况下的分支判断变得更简洁,可以避免大量使用 if-else if-else不断判断的情况,让代码看起来更简洁。

那么问题来了,switchif 语句间到底在底层实现上有什么区别呢?

直接说结论,其实答案还不是唯一,和编译器实现还有具体的 switch 有关,下面我们分别讨论。

以下以 ARM 汇编为例,别的架构的编译器可能会有不同的结果,不过应该大同小异。

2.1、case 数量情况少且 case 值比较连续的看情况

C代码:

switch (mode)
{
    case 0:
        count = 1;
        break;

    case 1:
        count = 2;
        break;

    case 2:
        count = 4;
        break;

    case 3:
        count = 4;
        break;

    default :
        break;
}

看汇编实现:

    51:         switch (mode) 
    52:         { 
    53:             case 0: 
0x0800018A B135      CBZ      r5,0x0800019A
0x0800018C 2D01      CMP      r5,#0x01
0x0800018E D006      BEQ      0x0800019E
0x08000190 2D02      CMP      r5,#0x02
0x08000192 D006      BEQ      0x080001A2
0x08000194 2D03      CMP      r5,#0x03
0x08000196 D108      BNE      0x080001AA
0x08000198 E005      B        0x080001A6
    54:                 count = 1; 
0x0800019A 2401      MOVS     r4,#0x01
    55:                 break; 
    56:              
    57:             case 1: 
0x0800019C E006      B        0x080001AC
    58:                 count = 2; 
0x0800019E 2402      MOVS     r4,#0x02
    59:                 break; 
    60:              
    61:             case 2: 
0x080001A0 E004      B        0x080001AC
    62:                 count = 4; 
0x080001A2 2404      MOVS     r4,#0x04
    63:                 break; 
    64:              
    65:             case 3: 
0x080001A4 E002      B        0x080001AC
    66:                 count = 4; 
0x080001A6 2404      MOVS     r4,#0x04
    67:                 break; 
    68:              
    69:             default : 
0x080001A8 E000      B        0x080001AC
    70:                 break; 
    71:         } 

我们看到使用了非常多的 CMP 指令,其实在这种情况下和使用多个 if-else if 一样。

2.2、case 数量情况多且 case 值比较连续的看情况

C 代码:

switch (mode)
{
    case 0:
        count = 1;
        break;

    case 1:
        count = 2;
        break;

    case 2:
        count = 4;
        break;

    case 3:
        count = 4;
        break;

    case 4:
        count = 1;
        break;

    case 6:
        count = 2;
        break;

    case 8:
        count = 4;
        break;

    case 10:
        count = 4;
        break;

    default :
        break;
}

我们来看下汇编:

    51:         switch (mode) 
    52:         { 
    53:             case 0: 
0x0800018A 2D0B      CMP      r5,#0x0B
0x0800018C D217      BCS      0x080001BE
0x0800018E E8DFF005  TBB      [pc,r5]
0x08000192 0806      DCW      0x0806
0x08000194 0C0A      DCW      0x0C0A
0x08000196 160E      DCW      0x160E
0x08000198 1610      DCW      0x1610
0x0800019A 1612      DCW      0x1612
0x0800019C 0014      DCW      0x0014
    54:                 count = 1; 
0x0800019E 2401      MOVS     r4,#0x01
    55:                 break; 
    56:              
    57:             case 1: 
0x080001A0 E00E      B        0x080001C0
    58:                 count = 2; 
0x080001A2 2402      MOVS     r4,#0x02
    59:                 break; 
    60:              
    61:             case 2: 
0x080001A4 E00C      B        0x080001C0
    62:                 count = 4; 
0x080001A6 2404      MOVS     r4,#0x04
    63:                 break; 
    64:              
    65:             case 3: 
0x080001A8 E00A      B        0x080001C0
    66:                 count = 4; 
0x080001AA 2404      MOVS     r4,#0x04
    67:                 break; 
    68:  
    69:             case 4: 
0x080001AC E008      B        0x080001C0
    70:                 count = 1; 
0x080001AE 2401      MOVS     r4,#0x01
    71:                 break; 
    72:              
    73:             case 6: 
0x080001B0 E006      B        0x080001C0
    74:                 count = 2; 
0x080001B2 2402      MOVS     r4,#0x02
    75:                 break; 
    76:              
    77:             case 8: 
0x080001B4 E004      B        0x080001C0
    78:                 count = 4; 
0x080001B6 2404      MOVS     r4,#0x04
    79:                 break; 
    80:              
    81:             case 10: 
0x080001B8 E002      B        0x080001C0
    82:                 count = 4; 
0x080001BA 2404      MOVS     r4,#0x04
    83:                 break;             
    84:             default : 
0x080001BC E000      B        0x080001C0
    85:                 break; 
    86:         } 
    87:     } 

我们可以看到 TBB(ARM M3/M4汇编指令TBB TBH实现复杂表格跳转)这个汇编指令, 这个指令用于 switch 跳转,其实现方式有些类似数组,我们将程序中的 case 值作为数组的索引,就可以立即得到目标值。

这种方式是采用空间换时间的方式,在占用更多内存的情况下,我们可以快速匹配到要执行的语句。

值得注意的是这里只要求 case 比较连续即可,即使这里举例用的是 100-104。这点也可以类比数组,将其映射到 0-4 即可。

2.3、 case 值跨度比较大的看情况

看 C 代码,只是将上文的 case 10 改为 case 50

switch (mode)
{
    case 0:
        count = 1;
        break;

    case 1:
        count = 2;
        break;

    case 2:
        count = 4;
        break;

    case 3:
        count = 4;
        break;

    case 4:
        count = 1;
        break;

    case 6:
        count = 2;
        break;

    case 8:
        count = 4;
        break;

    case 50:
        count = 4;
        break;

    default :
        break;
}

对应汇编:

    51: switch (mode) 
    52: { 
    53:     case 0: 
0x0800018A 2D04      CMP      r5,#0x04
0x0800018C D017      BEQ      0x080001BE
0x0800018E DC07      BGT      0x080001A0
0x08000190 B16D      CBZ      r5,0x080001AE
0x08000192 2D01      CMP      r5,#0x01
0x08000194 D00D      BEQ      0x080001B2
0x08000196 2D02      CMP      r5,#0x02
0x08000198 D00D      BEQ      0x080001B6
0x0800019A 2D03      CMP      r5,#0x03
0x0800019C D117      BNE      0x080001CE
0x0800019E E00C      B        0x080001BA
0x080001A0 2D06      CMP      r5,#0x06
0x080001A2 D00E      BEQ      0x080001C2
0x080001A4 2D08      CMP      r5,#0x08
0x080001A6 D00E      BEQ      0x080001C6
0x080001A8 2D32      CMP      r5,#0x32
0x080001AA D110      BNE      0x080001CE
0x080001AC E00D      B        0x080001CA
    54:         count = 1; 
0x080001AE 2401      MOVS     r4,#0x01
    55:         break; 
    56:  
    57:     case 1: 
0x080001B0 E00E      B        0x080001D0
    58:         count = 2; 
0x080001B2 2402      MOVS     r4,#0x02
    59:         break; 
    60:  
    61:     case 2: 
0x080001B4 E00C      B        0x080001D0
    62:         count = 4; 
0x080001B6 2404      MOVS     r4,#0x04
    63:         break; 
    64:  
    65:     case 3: 
0x080001B8 E00A      B        0x080001D0
    66:         count = 4; 
0x080001BA 2404      MOVS     r4,#0x04
    67:         break; 
    68:  
    69:     case 4: 
0x080001BC E008      B        0x080001D0
    70:         count = 1; 
0x080001BE 2401      MOVS     r4,#0x01
    71:         break; 
    72:  
    73:     case 6: 
0x080001C0 E006      B        0x080001D0
    74:         count = 2; 
0x080001C2 2402      MOVS     r4,#0x02
    75:         break; 
    76:  
    77:     case 8: 
0x080001C4 E004      B        0x080001D0
    78:         count = 4; 
0x080001C6 2404      MOVS     r4,#0x04
    79:         break; 
    80:  
    81:     case 50: 
0x080001C8 E002      B        0x080001D0
    82:         count = 4; 
0x080001CA 2404      MOVS     r4,#0x04
    83:         break; 
    84:  
    85:     default : 
0x080001CC E000      B        0x080001D0
    86:         break; 
    87: } 

我们看到这种情况下,编译器采用了和第一种举例的情况的实现方式,也就是使用类似 if-else if的方式。

至于为什么会这样,是编译器看到 case 的值相差太大,如果用第二种那种跳转表的方式,占用的空间会大大增大(类似数组,即使你只用了索引 026,起码你也得定义数组 arr[27],大大浪费别的25个数组值),聪明编译器又岂会不懂这个道理,所以选择了这种方式,就是在时间和空间中选择了节省大量空间。

总结

我们看到,switch 语句的具体实现其实是会根据不同的情况会有不同的实现。编译器真是个聪明的东西呢。

以前还注意到 switch 的实现竟然还有那么多的门道,要学的东西真多,自己动手去探究也更能够理解更深刻。

最后

以上就是粗暴宝马为你收集整理的C语言的 switch 语句的底层汇编具体实现(以ARM汇编为例讲解)一、参考资料二、具体分析总结的全部内容,希望文章能够帮你解决C语言的 switch 语句的底层汇编具体实现(以ARM汇编为例讲解)一、参考资料二、具体分析总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部