目录
2、当 case 的数量 / case 的范围 < 10%时,跳转表消失。
一、汇编器如何汇编switch?
1、把所有 case 的值从小到大排列。
2、如果 case 最小值 < 0,那么对所有 case 的值同时加上相同的值,使 case 最小值 = 0;
如果 case 最小值 > 0,那么对所有 case 的值同时减去相同的值,使 case 最小值 = 0。
这样一来,第一个case的值就刚好为 0。
二、什么是跳转表?
跳转表Jump Table中,每行放一个case代码段的首地址。
jmp *(jtab+8*x)
上面是通过跳转表找case的汇编指令。
jtab是跳转表的首地址。
在64位机器里,跳转表中每个(case代码段的)首地址大小为8字节(32位机器是4字节)。
x代表,我们要去第x个case。
所以,jtab+8*x 直接算出第x个代码段的首地址 Targ_x 放在哪块内存中(jtab+8*x就是那块内存的地址)。我们去那个内存里取出Targ_x。
接着,我们找到地址为 Targ_x 的内存。从这里开始,就是我们要找的 case 代码段 Block x。
三、什么时候跳转表消失?
1、当 case 的数量 <= 4 时,跳转表消失。
例如:
switch(i)
{
case 1:
j+=1;
break;
case 2:
j+=2;
break;
case 3:
j+=3;
break;
case 4:
j+=4;
break;
default:
j+=7;
break;
}
以上代码段只有4个case,所以汇编后跳转表消失。
2、当 case 的数量 / case 的范围 < 10%时,跳转表消失。
例如:
switch(i)
{
case 1:
j+=1;
break;
case 2:
j+=2;
break;
case 3:
j+=3;
break;
case 4:
j+=4;
break;
case 5:
j+=5;
case 61:
j+=61;
break;
default:
j+=5;
break;
}
case数量=6
case范围=61-1=60
6/60 = 10% = 10%
所以,以上代码段汇编后有跳转表。
switch(i)
{
case 1:
j+=1;
break;
case 2:
j+=2;
break;
case 3:
j+=3;
break;
case 4:
j+=4;
break;
case 5:
j+=5;
case 62:
j+=61;
break;
default:
j+=5;
break;
}
case数量=6
case范围=62-1=61
6/61 < 10%
所以,以上代码段汇编后跳转表消失。
当然,不同架构和不同版本下,情况可能会不一样。比如 10% 变成 8%,或者汇编后可能出现两个跳转表(我的版本最多只有一个)。
四、为什么汇编器要让跳转表消失?
.L4:
.quad .L2
.quad .L3
.quad .L5
.quad .L6
.quad .L7
.quad .L8
.quad .L2
.quad .L2
.quad .L2
.quad .L2
.quad .L2
.quad .L2
.quad .L2
.quad .L2
.quad .L2
上面是一个跳转表。
.L2 跳转到default段 看做无用的部分
.L3 ~ .L8 跳转到对应的case段 看做有用的部分
我们可以看到,比起用 if-else 一个个跳转,用上面的跳转表可以更快找到 case 代码段。这是用空间换取时间。
但是,如果跳转表中无用部分占比太多,汇编器就会认为用跳转表是不值得的。此时汇编器就会让跳转表消失,转而用类似 if-else 的方式找到 case 代码段,如下:
cmp eax, 3 //比较 eax 与 3
je .L3 //若 eax==3 跳到 L3 处。
cmp eax, 3 //比较eax与3
jg .L4 //若 eax>3 跳到 L4 处。
cmp eax, 1 //比较eax与1
je .L5 //若 eax==1 跳到 L5 处。
cmp eax, 2 //比较eax与2
je .L6 //若 eax==2 跳到 L6 处。
jmp .L2 //无条件跳到 L2