為了加速遊戲,一提起組合語言,大家也許會感到很神秘。其實如果你學起來就會發現,它並非想象中那樣難。特別是內嵌彙編,由於它和c++緊密結合,使你不必考慮很多煩瑣的細節(例如輸入輸出函式的寫法),學習起來比較容易。使用內嵌彙編,特別是使用mmx指令,可以大大提高各種遊戲中常見特效的速度,對於編出乙個漂亮的遊戲非常重要。學好組合語言還有乙個特別有趣的用處:可以觀察和看懂vc++生成的彙編**,從而更好地了解c++語言本身和優化**。
6.1 內嵌彙編簡介
在高階語言中,我們可以無所顧忌地使用各種語句,再由編譯器將語句經過非常複雜的編譯過程將其轉換為機器指令後執行。事實上,處理器本身所能處理的指令不多;更糟糕的是,大部分指令不能直接施用在記憶體中的變數上,要借助暫存器這個中間儲存單元(你可以把暫存器看做是乙個變數)。pentium級處理器的暫存器不多,只有8個32位通用暫存器,分別被稱為eax, ebx, ecx, edx, ebp, esp, edi , esi。每乙個通用暫存器的低16位又分別被稱為ax, bx, cx, dx, bp, sp, di , si。其中ax, bx, cx, dx的高8位被稱為ah, bh, ch, dh;低8位被稱為al, bl, cl, dl。注意在內嵌彙編中不應使用ebp和esp,它們儲存著重要的堆疊資訊。
還有乙個非常重要的暫存器,叫做標誌暫存器(eflags),標明了運算結果的各個屬性,你不能直接讀取或修改它。這些屬性有:不溢位/溢位(of)、正/負(sf)、非零/零(zf)、偶/奇(pf)、不進製/進製(cf)等。
組合語言中若要表示有符號整數,需先寫出該整數的絕對值的二進位制形式,若此數為正數或零則已得到結果,否則將其取反(0->1,1->0)後再加上一即為結果。所以乙個8位暫存器可表示的有符號整數範圍為從-128到127。
與c++類似,組合語言提供了得到指標所指記憶體的方法,這被稱為"定址"。用法很簡單,象這樣:[暫存器+暫存器*1/2/4/8+32位立即數]就可以得到這個位置的數了。舉乙個例子,如果有乙個陣列unsigned short a[100],且eax中儲存著a[0]的位址,那麼[eax+58]即為a[29]的值;如果此時ebx=9,那麼[eax+ebx*2+4]將是a[11]的值。
那麼又怎麼把乙個變數的位址裝載進暫存器呢?後面將會介紹。
內嵌彙編的使用方法是:
_asm
你可以把它插入程式中的任何位置,非常靈活。
6.2 基本指令
基本指令均不影響標誌暫存器。
第一條指令是傳送指令:mov dest, src。其作用為將dest賦以值src。其中dest和src可為整數(稱為立即數)、變數或[位址](儲存器),暫存器。需注意的是有的操作是不允許的:在組合語言中你永遠不能將儲存器或暫存器內容賦給立即數(你見過5=a這樣的語句嗎?);也不能將儲存器內容直接賦給另一儲存器,必須借助暫存器作為中間變數來實現。關於mov還有一點要注意的是dest和src必須都為32位/16位/8位,即同一大小。值得特別注意的是,資料在記憶體中的儲存方式是以位元組為單位顛倒的,即:如果記憶體位址0000儲存的位元組是5f,位址0001儲存的位元組是34,位址0002儲存的位元組是6a,位址0003儲存的位元組是c4,那麼位址0000處儲存的字(word,16位)為345f,雙字(dword,32位)為c46a345f。
第二條指令是位址裝載指令:lea a, b。其作用為將b變數的位址裝載進a暫存器(a需為32位)。要注意的是不能像lea eax, temp[5]這樣直接調陣列中某個元素的位址。這個指令還可以用來進行簡單的運算,考慮下面的語句:lea eax, [ebx+ecx*4+8],此語句可將ebx+ecx*4+8的值賦給eax。
ok,讓我們看乙個可以將兩個正整數相加的程式:
#include
using namespace std;
//此程式也展示了內嵌彙編應如何使用c++中的指標
void main( )
//內嵌彙編部分結束...
cout<6.4 邏輯與移位指令
邏輯指令會將標誌暫存器中的of和cf清零。
表6.3
not a a=~a(注意not與neg不同!)
and a, b a=a&b
or a, b a=a|b
xor a, b a=a^b
下面是移位指令,其中x可為8位立即數或cl暫存器。
表6.4
sal(也可寫成shl) a, x 將a左移x位,cf=移出的那一位數空位用0補足
sar a, x 將有符號a右移x位,cf=移出的那一位數空位按a的符號用0/1補足
shr a, x 將無符號a右移x位,cf=移出的那一位數空位用0補足
rol a, x 將a迴圈左移(左邊出去的數又從最右邊回來)
ror a, x 將a迴圈右移(右邊出去的數又從最左邊回來)
rcl / rcr a, x 把cf放在目標最左邊然後迴圈左/右移
shld a, b, x 將a左移x位, 空出位用b高階m位填充例:shld edx, eax, 16可將eax的高16位 放入dx中。
shrd a, b, x 將a右移x位, 空出位用b低端m位填充
6.5 比較、測試、轉移與迴圈指令
比較與測試指令基本上總是與轉移指令相配合使用,其形式分別為cmp a, b和test a, b。cmp實際上是根據a-b的值改變標誌暫存器但不改變a和b,可以檢測兩個數的大小關係。test則是根據a&b的值改變標誌暫存器,同樣不改變a和b。這條指令可以用來測試a中哪些位為1。執行完這些指令後,立刻用轉移指令就可實現條件轉移,因為條件轉移語句會根據標誌暫存器決定是否轉移。轉移指令的使用方法就像這樣:
__asm
轉移指令有:
jmp 無條件轉移
je / jz zf=1時轉移
jne / jnz zf=0時轉移
js sf=1時轉移
jns sf=0時轉移
jo of=1時轉移
jno of=0時轉移
jp / jpe pf=1時轉移
jnp / jpo pf=0時轉移
根據兩無符號數關係轉移:
ja / jnbe 大於時轉移 (cf或zf=0)
jbe / jna 不大於時轉移 (cf或zf=1)
jb / jnae / jc 小於時轉移 (cf=1)
jnb / jae / jnc 不小於時轉移 (cf=0)
根據兩有符號數關係轉移:
jnle / jg 大於時轉移 ((sf異或of)或zf)=0 )
jle / jng 不大於時轉移 ((sf異或of)或zf)=1 )
jl / jnge 小於時轉移 (sf異或of=1)
jnl / jge 不小於時轉移 (sf異或of=0)
特殊轉移語句:
jecxz cx=0時轉移
為了記住這麼多條指令,你只需知道一點,就是無符號數之間的關係分別被稱為above,equal,below,分別代表大於,等於,小於;有符號數之間相應的關係則分別被稱為great,equal,less。
事實上,有些轉移是可以避免的。舉個例子,要算乙個數的絕對值是否要用轉移呢?請看一段程式:
mov edx,eax
sar edx,31 //edx現在全為eax的符號位
xor eax,edx
sub eax,edx
找出兩個數中較大的乙個應該要用轉移吧?不過也可以象下面的解決方案那樣利用標誌,真是絕了:
sub ebx,eax
sbb ecx,ecx //如果ebx≥eax,現在ecx=0,否則ecx=ffffffff
and ecx,ebx
add eax,ecx
下面的一段程式實現了if (a != 0) a = b; else a = c;
cmp eax,1
sbb eax,eax
xor ecx,ebx
and eax,ecx
xor eax,ebx
迴圈語句常用的是loop,它等價於dec cx加上jnz。
下面看乙個彙編的綜合運用:氣泡排序。
#include
using namespace std;
#define array_size 10
int a[array_size]=;
void main()
for (int i=0;i<10;i++)
cout<}
彙編中的暫存器說明
組合語言和cpu以及記憶體,埠等硬體知識是連在一起的.這也是為什麼組合語言沒有通用性的原因.下面簡單講講基本知識 針對intel x86及其相容機 x86組合語言的指令,其操作物件是cpu上的暫存器,系統記憶體,或者立即數.有些指令表面上沒有運算元,或者看上去缺少運算元,其實該指令有內定的操作物件,...
if語句的彙編表示
gcc產生的 可以使用objdump 檢視它對應的彙編 gcc檢視彙編 本文主要介紹條件語句if 語句的彙編表示 有以下 int max int x,int y 產生的彙編 如下 00000000 0 55 push ebp 1 89 e5 mov esp,ebp 3 8b 45 08 mov 0x...
if語句的彙編表示
demo c語言 int max int x,int y 產生的彙編 如下 00000000 0 55 push ebp 1 89 e5 mov esp,ebp 3 8b 45 08 mov 0x8 ebp eax 6 3b 45 0c cmp 0xc ebp eax 9 7e 05 jle 10 ...