眾所周知,任何程序都可以由三種基本控制結構組成,分別是循序結構,選擇結構,循環結構。
這三種結構翻譯成匯編語言又是怎樣的呢?這裡主要考慮的是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將直接被優化掉,畢竟使用流水線速度將會大大加快,忽然一個跳轉將會打斷流水線。