switch的底层实现机制根据switch需要判断的表达式的复杂性和不同编译器有所不同。一般情况下,switch有三种实现机制,分别是直接判断、跳转表和树型结构。编译器根据需要判断表达式的复杂性并采用相应的的实现方式。下面分别研究这三种switch机制
(1)直接判断
int main() { int i=1; switch(i) { case 1:printf("%s","no1");break; case 2:printf("%s","no2");break; default:printf("%s","default"); } return 0; }
int main() { 00F013A0 push ebp 00F013A1 mov ebp,esp 00F013A3 sub esp,0D0h 00F013A9 push ebx 00F013AA push esi 00F013AB push edi 00F013AC lea edi,[ebp-0D0h] 00F013B2 mov ecx,34h 00F013B7 mov eax,0CCCCCCCCh 00F013BC rep stos dword ptr es:[edi] int i=1; 00F013BE mov dword ptr [i],1 switch(i) 00F013C5 mov eax,dword ptr [i] 00F013C8 mov dword ptr [ebp-0D0h],eax 00F013CE cmp dword ptr [ebp-0D0h],1 ;比较i和1 00F013D5 je main+42h (0F013E2h) ;i==1则跳转到0F013E2 00F013D7 cmp dword ptr [ebp-0D0h],2 ;比较i和2 00F013DE je main+60h (0F01400h) ;相等则跳到0F01400 00F013E0 jmp main+7Eh (0F0141Eh) ;无条件跳到0F0141E { case 1:printf("%s","no1");break; 00F013E2 mov esi,esp ;输出no1 00F013E4 push offset string "no1" (0F05750h) 00F013E9 push offset string "%s" (0F0574Ch) 00F013EE call dword ptr [__imp__printf (0F082BCh)] 00F013F4 add esp,8 00F013F7 cmp esi,esp 00F013F9 call @ILT+310(__RTC_CheckEsp) (0F0113Bh) 00F013FE jmp main+9Ah (0F0143Ah) ;跳到结束 case 2:printf("%s","no2");break; 00F01400 mov esi,esp ;输出no2 00F01402 push offset string "no2" (0F05748h) 00F01407 push offset string "%s" (0F0574Ch) 00F0140C call dword ptr [__imp__printf (0F082BCh)] 00F01412 add esp,8 00F01415 cmp esi,esp 00F01417 call @ILT+310(__RTC_CheckEsp) (0F0113Bh) 00F0141C jmp main+9Ah (0F0143Ah) ;跳到结束 default:printf("%s","default"); 00F0141E mov esi,esp ;输出default 00F01420 push offset string "default" (0F0573Ch) 00F01425 push offset string "%s" (0F0574Ch) 00F0142A call dword ptr [__imp__printf (0F082BCh)] 00F01430 add esp,8 00F01433 cmp esi,esp 00F01435 call @ILT+310(__RTC_CheckEsp) (0F0113Bh) } return 0; 00F0143A xor eax,eax } 00F0143C pop edi 00F0143D pop esi 00F0143E pop ebx 00F0143F add esp,0D0h 00F01445 cmp ebp,esp 00F01447 call @ILT+310(__RTC_CheckEsp) (0F0113Bh) 00F0144C mov esp,ebp 00F0144E pop ebp 00F0144F ret
当程序中不存在break的时候,反汇编中对应的跳到结束的jmp也就不存在。以上反汇编代码与if···else类似。
(2)跳转表
int main() { int i=1; switch(i) { case 1:printf("%s","no1");break; case 2:printf("%s","no2");break; case 3:printf("%s","no3");break; case 4:printf("%s","no4");break; case 5:printf("%s","no5");break; case 6:printf("%s","no6");break; default:printf("%s","default"); } return 0; }
int main() { 008213A0 push ebp 008213A1 mov ebp,esp 008213A3 sub esp,0D0h 008213A9 push ebx 008213AA push esi 008213AB push edi 008213AC lea edi,[ebp-0D0h] 008213B2 mov ecx,34h 008213B7 mov eax,0CCCCCCCCh 008213BC rep stos dword ptr es:[edi] int i=1; 008213BE mov dword ptr [i],1 switch(i) 008213C5 mov eax,dword ptr [i] ;eax存放i 008213C8 mov dword ptr [ebp-0D0h],eax ;设局部变量[ebp-0D0h]=eax 008213CE mov ecx,dword ptr [ebp-0D0h] ;ecx=i 008213D4 sub ecx,1 ;ecx=i-1 008213D7 mov dword ptr [ebp-0D0h],ecx ;i=i-1 008213DD cmp dword ptr [ebp-0D0h],5 ;i与5比较 008213E4 ja $LN2+1Eh (8214B1h) ;如果i>5则跳转到输出default 008213EA mov edx,dword ptr [ebp-0D0h] ;edx=i 008213F0 jmp dword ptr (8214E4h)[edx*4] ;跳转到跳转表所指定的地址 { case 1:printf("%s","no1");break; 008213F7 mov esi,esp 008213F9 push offset string "no1" (825760h) 008213FE push offset string "%s" (82575Ch) 00821403 call dword ptr [__imp__printf (8282BCh)] 00821409 add esp,8 0082140C cmp esi,esp 0082140E call @ILT+310(__RTC_CheckEsp) (82113Bh) 00821413 jmp $LN2+3Ah (8214CDh) case 2:printf("%s","no2");break; 00821418 mov esi,esp 0082141A push offset string "no2" (825758h) 0082141F push offset string "%s" (82575Ch) 00821424 call dword ptr [__imp__printf (8282BCh)] 0082142A add esp,8 0082142D cmp esi,esp 0082142F call @ILT+310(__RTC_CheckEsp) (82113Bh) 00821434 jmp $LN2+3Ah (8214CDh) case 3:printf("%s","no3");break; 00821439 mov esi,esp 0082143B push offset string "no3" (825754h) 00821440 push offset string "%s" (82575Ch) 00821445 call dword ptr [__imp__printf (8282BCh)] 0082144B add esp,8 0082144E cmp esi,esp 00821450 call @ILT+310(__RTC_CheckEsp) (82113Bh) 00821455 jmp $LN2+3Ah (8214CDh) case 4:printf("%s","no4");break; 00821457 mov esi,esp 00821459 push offset string "no4" (825750h) 0082145E push offset string "%s" (82575Ch) 00821463 call dword ptr [__imp__printf (8282BCh)] 00821469 add esp,8 0082146C cmp esi,esp 0082146E call @ILT+310(__RTC_CheckEsp) (82113Bh) 00821473 jmp $LN2+3Ah (8214CDh) case 5:printf("%s","no5");break; 00821475 mov esi,esp 00821477 push offset string "no5" (82574Ch) 0082147C push offset string "%s" (82575Ch) 00821481 call dword ptr [__imp__printf (8282BCh)] 00821487 add esp,8 0082148A cmp esi,esp 0082148C call @ILT+310(__RTC_CheckEsp) (82113Bh) 00821491 jmp $LN2+3Ah (8214CDh) case 6:printf("%s","no6");break; 00821493 mov esi,esp 00821495 push offset string "no6" (825748h) 0082149A push offset string "%s" (82575Ch) 0082149F call dword ptr [__imp__printf (8282BCh)] 008214A5 add esp,8 008214A8 cmp esi,esp 008214AA call @ILT+310(__RTC_CheckEsp) (82113Bh) 008214AF jmp $LN2+3Ah (8214CDh) default:printf("%s","default"); 008214B1 mov esi,esp 008214B3 push offset string "default" (82573Ch) 008214B8 push offset string "%s" (82575Ch) 008214BD call dword ptr [__imp__printf (8282BCh)] 008214C3 add esp,8 008214C6 cmp esi,esp 008214C8 call @ILT+310(__RTC_CheckEsp) (82113Bh) } return 0; 008214CD xor eax,eax } 008214CF pop edi 008214D0 pop esi 008214D1 pop ebx 008214D2 add esp,0D0h 008214D8 cmp ebp,esp 008214DA call @ILT+310(__RTC_CheckEsp) (82113Bh) 008214DF mov esp,ebp 008214E1 pop ebp 008214E2 ret
通过查看内存数据得知8214E4h处的数据正是上面反汇编代码中那6个字符串输出函数地址。如果switch语句需要比较的表达式常量很多,而且这些表达式常量基本是按顺序排列的,那么使用跳转表将会大大提高效率。