【转帖】Switch结构学习笔记今天天这堂课给我感触挺大的:本来以为自己用OD调试过switch结构,以为对它十分理解了。结果按照钱老师讲的,自己再次调试了一下switch,结果发现我以前是多么的不求甚解。╮(╯▽╰)╭~记录这个笔记,算是给自己一个警示吧,以后学习的时候,不能浮躁……一、case小于等于3项的情况先看第一个程序段:switch(nscore){case1:ntmpNum=1;break;case3:ntmpNum=3;break;case4:ntmpNum=4;break;default:ntmpNum=10;}printf(%d,ntmpNum);//要调用一下ntmpNum,否则上面的switch会被优化掉OD载入,看一下:00401013|.E80F010000call00401127;scanf00401018|.8B442408moveax,dwordptr[esp+8]0040101C|?83C408addesp,8;上面scanf是C类调用0040101F|?48deceax;通过EAX的减法来判断属于哪个分支00401020|?741Djeshort0040103F00401022|.83E802subeax,200401025\.7411jeshort004010380040102748deceax004010287407jeshort004010310040102AB80A000000moveax,0A0040102FEB13jmpshort00401044;break00401031|.B804000000moveax,400401036|.EB0Cjmpshort0040104400401038|?B803000000moveax,30040103D|?EB05jmpshort004010440040103F|?B801000000moveax,100401044|.50pusheax00401045|?6838904000push00409038;ASCII%d这种情况跟if差不多,不多分析。二、case项多于3项且有规律的情况看第二段代码:scanf(%d,&nscore);switch(nscore){case3:ntmpNum=1;break;case1:ntmpNum=3;break;case5:ntmpNum=4;break;case9:ntmpNum=4;break;case7:ntmpNum=4;break;case11:ntmpNum=4;break;default:ntmpNum=10;}printf(%d,ntmpNum);//要调用一下ntmpNum,否则上面的switch会被优化掉这段代码,我们将有规律的case打乱顺序,然后看编译器是怎么处理的。OD中查看反汇编形式:0040100E|.6838904000push00409038;ASCII%d00401013|.E83F010000call00401157;scanf00401018|.8B4C2408movecx,dwordptr[esp+8];得到输入的内容0040101C|.83C408addesp,80040101F|.8D41FFleaeax,dwordptr[ecx-1];输入的内容-1;Switch(cases1..B)00401022|.83F80Acmpeax,0A00401025|.771Cjashort0040104300401027|.FF2485641040jmpdwordptr[eax*4+401064];查表,跳转到对应的CASE中0040102E|B801000000moveax,1;Case3ofswitch0040101F00401033|.EB13jmpshort0040104800401035|B803000000moveax,3;Case1ofswitch0040101F0040103A|.EB0Cjmpshort004010480040103C|B804000000moveax,4;Cases5,7,9,Bofswitch0040101F00401041|.EB05jmpshort0040104800401043|B80A000000moveax,0A;Defaultcaseofswitch0040101F00401048|50pusheax00401049|.6838904000push00409038;ASCII%d0040104E|.E8D3000000call00401126;printf跟随下这个表,我们发现,这个表就在调用它的函数后,如下:跳转表:0040106400401035switch.004010350040106800401043switch.00401043插入的是default分支的首地址0040106C0040102Eswitch.0040102E0040107000401043switch.00401043插入的是default分支的首地址004010740040103Cswitch.0040103C0040107800401043switch.00401043插入的是default分支的首地址0040107C0040103Cswitch.0040103C0040108000401043switch.00401043插入的是default分支的首地址004010840040103Cswitch.0040103C0040108800401043switch.00401043插入的是default分支的首地址0040108C0040103Cswitch.0040103C认真对比一下这个表,发现,它先是对case后的常量排序,然后再将对应的处理代码的首地址写成一个表,通过jmpdwordptr[eax*4+401064]查表直接进入到对应的case中。对于缺省的case(我们是间隔2递增的case)在表中填充的是default分支的首地址。三、多于三项部分有规律的情况上个我们发现,它会给缺省的case表项中填补default分支的首地址,那我们将这个间隔调大,观察一下编译器会怎么处理,代码段如下:scanf(%d,&nscore);switch(nscore){case1:ntmpNum=1;break;case2:ntmpNum=2;break;case3:ntmpNum=3;break;//这里丢失20多个casecase26:ntmpNum=26;break;case27:ntmpNum=27;break;case28:ntmpNum=28;break;default:ntmpNum=10;}printf(%d,ntmpNum);//要调用一下ntmpNum,否则上面的switch会被优化掉反汇编观察一下:0040100E6838904000push00409038;ASCII%d00401013E86F010000call00401187;scanf004010188B4C2408movecx,dwordptr[esp+8];得到输入的内容0040101C83C408addesp,80040101F8D41FFleaeax,dwordptr[ecx-1];输入的内容-10040102283F81Bcmpeax,1B004010257739jashort004010600040102733D2xoredx,edx004010298A909C104000movdl,byteptr[eax+40109C];检索case的下标索引值表;它参与运算从地址表中找到对应的case地址0040102FFF249580104000jmpdwordptr[edx*4+401080];通过值表填充的CASE索引值,查地址表00401036B801000000moveax,10040103BEB28jmpshort00401065;break0040103DB802000000moveax,200401042EB21jmpshort0040106500401044B803000000moveax,300401049EB1Ajmpshort004010650040104BB81A000000moveax,1A00401050EB13jmpshort0040106500401052B81B000000moveax,1B00401057EB0Cjmpshort0040106500401059B81C000000moveax,1C0040105EEB05jmpshort0040106500401060B80A000000moveax,0A0040106550pusheax004010666838904000push00409038;ASCII%d下标索引表:0040109C00DB00case1的索引值0040109D01DB01case2的索引值0040109E02DB02case3的索引值0040109F06DB06下面全部填充default的索引值004010A006DB06...004010B406DB06004010B503DB03case4的索引值004010B604DB04case5的索引值004010B705DB05case6的索引值跳转地址表:0040108000401036switch.00401036004010840040103Dswitch.0040103D0040108800401044switch.004010440040108C0040104Bswitch.0040104B0040109000401052switch.004010520040109400401059switch.004010590040109800401060switch.00401060这样查两个表,缺省的case项在索引表中插入default的索引值,这样每个case项就节省了3个字节的空间。movdl,byteptr[eax+40109C]//40109C是索引表首地址jmpdwordptr[edx*4+401080]//401080是跳转地址表的首地址。这样,就可以定位到对应的case项了。我们继续增大这个case之间的差距,让它超过255,代码段如下:scanf(%d,&nscore);switch(nscore){case1:ntmpNum=1;break;case2:ntmpNum=2;break;case3:ntmpNum=3;break;//这里丢失几个casecase326:ntmpNum=26;break;case327:ntmpNum=27;break;case328:ntmpNum=28;break;default:ntmpNum=10;}printf(%d,ntmpNum);//要调用一下ntmpNum,否则上面的switch会被优化掉反汇编看一下效果:004010188B442408moveax,dwordptr[esp+8];得到输入的内容0040101C83C408addesp,80040101F3D46010000cmpeax,146;判断是不是大case中最小的004010247F27jgshort0040104D;如果大于,就进入大case中比较00401026741Ejeshort00401046;如果相等就直接进入0x146的case代码段0040102848deceax;否则就到小的case段中比较。004010297414jeshort0040103F0040102B48deceax0040102C740Ajeshort004010380040102E48deceax0040102F7526jnzshort00401057;default了。00401031B803000000moveax,300401036EB32jmpshort0040106A00401038B802000000m