眾所周知,任何程序都可以由三種基本控制結構組成,分別是循序結構,選擇結構,循環結構。
這三種結構翻譯成匯編語言又是怎樣的呢?這裡主要考慮的是debug版本。對於release版本經過各種優化後結果不一樣,不作考慮。這裡的編譯器采用的是Visual Studio 2008
順序結構沒什麼懸念,這裡就不提了,首先看下選擇結構。
選擇結構,主要有兩種表現方式:if{ }else if{ } else{ }與 switch{case : case : default:}
首先來看下
if (a > 0 && b < 0) 00182DCC cmp dword ptr [a],0 ;兩個判斷,不合規范就跳到下一個else if處 00182DD0 jle foo+51h (182DF1h) 00182DD2 cmp dword ptr [b],0 00182DD6 jge foo+51h (182DF1h) { ;沒跳走,執行代碼塊的內容 printf("if (a > 0 && b < 0)"); 00182DD8 mov esi,esp 00182DDA push offset string "if (a > 0 && b < 0)" (185974h) 00182DDF call dword ptr [__imp__printf (1882B4h)] 00182DE5 add esp,4 00182DE8 cmp esi,esp 00182DEA call @ILT+450(__RTC_CheckEsp) (1811C7h) 00182DEF jmp foo+87h (182E27h) ;執行完,跳出if } else if (a < 0) ;還是判斷,不合規范就跳到下一個else 00182DF1 cmp dword ptr [a],0 00182DF5 jge foo+70h (182E10h) { printf("else if (a < 0)"); 00182DF7 mov esi,esp 00182DF9 push offset string "else if (a < 0)" (1857A8h) 00182DFE call dword ptr [__imp__printf (1882B4h)] 00182E04 add esp,4 00182E07 cmp esi,esp 00182E09 call @ILT+450(__RTC_CheckEsp) (1811C7h) } else 00182E0E jmp foo+87h (182E27h) ;我這得這條語句放在前一個else if裡頭更合適 { printf("else"); 00182E10 mov esi,esp 00182E12 push offset string "else" (1857A0h) 00182E17 call dword ptr [__imp__printf (1882B4h)] 00182E1D add esp,4 00182E20 cmp esi,esp 00182E22 call @ILT+450(__RTC_CheckEsp) (1811C7h) }
隨便寫的一個if循環,合理安排if的比較會讓代碼更少,但為了演示,也無所謂了。代碼分析完也覺得簡單,就是一開始看有點麻煩。
cmp <條件> ;多少個條件就多少個判斷跳轉 jle <下一個分支> ;這裡通常與C/C++裡的判斷相反 …… cmp <條件> jle <下一個分支> (代碼塊) jmp;最後一個if(else)代碼塊沒有這條
switch(a) 00E82DC5 mov eax,dword ptr [a] 00E82DC8 mov dword ptr [ebp-0D0h],eax 00E82DCE cmp dword ptr [ebp-0D0h],0 ;判斷跳轉很頻繁,首先考慮是switch 00E82DD5 je foo+4Bh (0E82DEBh) 00E82DD7 cmp dword ptr [ebp-0D0h],1 00E82DDE je foo+52h (0E82DF2h) ;這些是符合條件的就跳轉到對應的代碼塊 00E82DE0 cmp dword ptr [ebp-0D0h],2 00E82DE7 je foo+5Bh (0E82DFBh) 00E82DE9 jmp foo+64h (0E82E04h) ;沒有符合條件的,跳到 default { case 0: a = 0; 00E82DEB mov dword ptr [a],0 ;這裡沒有break,繼續往下執行 case 1: a =1; 00E82DF2 mov dword ptr [a],1 break; 00E82DF9 jmp foo+6Bh (0E82E0Bh) ;break,跳出switch case 2: a =2; 00E82DFB mov dword ptr [a],2 break; 00E82E02 jmp foo+6Bh (0E82E0Bh) default: a =3; 00E82E04 mov dword ptr [a],3 }
連續的比較與條件跳轉,容易讓人聯想到switch 對於代碼塊也比較簡單 有break會增加一個無條件跳轉
接下來看下循環結構,
循環結構主要有三種:For循環,While循環,Do-While循環。
至於其他語言的一些repeat until等不作考慮,請自行分析。
For循環
for (int i = 0;i< 5;i++) 00DB17CE mov dword ptr [i],0 00DB17D5 jmp foo+30h (0DB17E0h) 00DB17D7 mov eax,dword ptr [i] 00DB17DA add eax,1 00DB17DD mov dword ptr [i],eax 00DB17E0 cmp dword ptr [i],5 00DB17E4 jge foo+53h (0DB1803h) { printf("%d",i); 00DB17E6 mov esi,esp 00DB17E8 mov eax,dword ptr [i] 00DB17EB push eax 00DB17EC push offset string "%d" (0DB573Ch) 00DB17F1 call dword ptr [__imp__printf (0DB82B4h)] 00DB17F7 add esp,8 00DB17FA cmp esi,esp 00DB17FC call @ILT+450(__RTC_CheckEsp) (0DB11C7h) 00DB1801 jmp foo+27h (0DB17D7h) }
for(第一部分;第二部分;第三部分) { 循環體; }
mov <循環變量>,<初始值> ;第一部分。給循環變量賦初值 jmp B ;跳到第一次循環處,執行第二部分 A: (改動循環變量) ;第三部分。修改循環變量 B: cmp <循環變量>,<限制變量> ;第二部分。檢查循環變量 jge 跳出循環 ;這裡的判斷條件通常與for中看到的相反 …… (循環體) …… jmp A ;跳回去第三部分,修改變量循環
while(a > 0) 00852DC5 cmp dword ptr [a],0 ;首先判斷,不合條件跳出while代碼塊 00852DC9 jle foo+36h (852DD6h) { a--; 00852DCB mov eax,dword ptr [a] 00852DCE sub eax,1 00852DD1 mov dword ptr [a],eax } 00852DD4 jmp foo+25h (852DC5h) ;強制跳轉會開頭的判斷
框架很簡單,先判斷,不符合條件就跳出代碼塊,否則繼續執行下去,代碼塊最後跳回來繼續判斷。
A: cmp <循環變量>,<限制變量> jge B ;跳出代碼塊 (循環體) …… jmp A ;往回跳 B: ;循環結束
do { a--; 01362DC5 mov eax,dword ptr [a] 01362DC8 sub eax,1 01362DCB mov dword ptr [a],eax }while(a > 0); 01362DCE cmp dword ptr [a],0 01362DD2 jg foo+25h (1362DC5h)
Do - while就更加簡單了,直接把判斷挪到代碼塊後面。
A: cmp <循環變量>,<限制變量> jge B (循環體) …… jmp A B: ;循環結束
好了,基本介紹完了,此次只是為了告誡我們,在匯編裡面,代碼的理解跟高級語言是有點出入的,得轉換下思維。另外,也說明了同一種邏輯可以通過多種方式來表達。
另外,這只是debug版本下的反匯編代碼,在release版本下,代碼將千變萬化,比如,switch將可能會使用跳轉表等來實現,部分if將直接被優化掉,畢竟使用流水線速度將會大大加快,忽然一個跳轉將會打斷流水線。