switch语句

  • switch语句有几种特别的使用情况:
  1. 可以联系多个选项。如5、6的处理方式相同
  2. 可以实现落下,即实现语句块的穿越。如2没有break语句,因而执行完2会继续执行3
  • 在C语言中,switch选项必须为整数类型,不支持其他类型。
  • 本质上说,switch的所有功能都可被条件分支if-else语句所表达,那么既然有等价表示方式,为何要定义switch语法呢?
  1. 首先,要从switch语句机器指令的实现方式来讲。switch语句通过跳转表这种数据结构实现机器指令。跳转表是一个数组,其中每一个元素都是地址,指向对应case语句块的起始地址。switch语句工作时,根据x变量值,到跳转表中查到对应的元素,即case语句块的目标地址,然后执行跳转语句,跳转到真正目标入口的起始位置上。
  2. 这种实现方式,致使switch语句相比if-else有很大优势,因为switch语句不管跳转到哪个语句块,花费的时间是固定的,即时间复杂度为常数类型。而if-else如执行第n个语句块时,必须先做前n个语句块的判断,时间复杂度不固定。所以说,switch语句利用跳转表,将时间复杂度转换为了常数
  3. 这种实现方式,也决定了变量必须为整数。因为只有整数才能对数组下标进行查找操作

switch的机器指令实现

进入switch语句前,在汇编级别上做的准备工作

  • 首先传入用于判断的变量X,存在rdi寄存器中
  • 用ja指令排除掉小于0和大于6的数。ja指令用于无符号数大于的判断,尽管x为有符号数,也会被当做无符号数判断,若x为负数,会被ja当做最高位为1的正数,故而用ja指令可排除掉小于0和大于6的数
  • jmp间接跳转
  1. 何为间接跳转?跳转语句有两种分类方式,一种分为无条件跳转和有条件跳转;另一种则根据跳转目标,分为直接跳转和间接跳转。跳转目标是确定位置为直接跳转;跳转目标需要基于某个变量计算出来的,为间接跳转
  2. jmp间接跳转指令的操作数是一个寻址方式的四元组。其中:
  • L4是跳转表的起始地址。跳转表是编译器编译时生成的一组固定只读数据,一共有7个。编译器在内存中分配出一个不可被修改的区域,其起始地址标号为L4,每个数据宽度为8字节,对应数据值称为标签,标签实质为一个地址,标签地址就是跳转代码块的地址。
  • rdi是跳转表中要查找数据的下标,8代表每个元素大小,*表示把地址中数据取出来(上图中为L3),作为跳转语句的目标地址
  • 由于跳转表只有7个元素,所以switch最多只支持0到6范围的变化,小于0或大于6会调到默认位置,即default(也解释了ja语句的作用)

switch语句实例分析

  • 下图为跳转表与case语句块的对应关系
  • 对2、3落下的理解:

把w赋值为1,是在进入switch语句块时进行,因为并非所有语句块都需对w赋值操作(如2),如果在switch语句块前就对w赋值的话,会浪费指令,导致性能下降。而先检查哪个语句块需要赋值,再按需赋值,尽管会稍稍增加代码量,但大大加快效率

对switch的进一步探讨

  • 查找表(跳转表)实现switch语句有性能提升,但本身仍具有一定局限性。当case语句的值较大时,若仍然以0为起始地址,查找表规模会很大,且大部分地址实际上都指向default,这些地址的存储毫无意义。
  • 两种解决方法:
  1. 当case语句取址密集时,可认为是从0地址的一个整体偏移。此时只需要编译器减去一个固定偏移量,就可使case语句的值处于较小区间内。
  2. 当case数据范围很大,且在数轴上不密集时,就不适宜用查找表方式实现。此时我们使用二分搜索查找对应语句块。
关注公众号,让我们携手共进0.5.jpg

无欲则刚
76 声望16 粉丝