內嵌彙編簡介

     在高級語言中,我們可以無所顧忌地使用各種語句,再由編譯器將語句經過非常複雜的編譯過程將其轉換爲機器指令後運行。事實上,處理器本身所能處理的指令不多;更糟糕的是,大部分指令不能直接施用在內存中的變量上,要藉助寄存器這個中間存儲單元(你可以把寄存器看做是一個變量)。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
{
語句 //後面可加可不加分號
}
你可以把它插入程序中的任何位置,非常靈活。

  

基本指令
基本指令均不影響標誌寄存器。
     第一條指令是傳送指令: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 <iostream>
using namespace std;

void main( )
{
 int a,b;
 cin>>a;
 cin>>b;
 int *c = &a;
 __asm    //下面是內嵌彙編...
 {
  mov eax, c;  //c中存儲的a的地址->eax
  mov eax, [eax]; //a的值->eax //注意直接mov eax, [c]是錯誤的
  mov ebx, b;  //可以像這樣直接對ebx賦值
  lea eax, [eax+ebx];
  mov a, eax;  //將eax的值->a
 }     //內嵌彙編部分結束...
 cout<<a<<endl;
}

 

     第三條指令是交換指令,形式爲XCHG A, B。A和B中至少有一個須爲寄存器。如果你想交換兩處內存中的數據則要使用寄存器作爲中間人。
     接着是擴展傳送指令,共有兩條,爲MOVSX DEST, SRC和MOVZX DEST, SRC,它們的用處分別是將SRC中的有符號數或無符號數賦給DEST。這時你就可以將字長較短的寄存器的內容賦給字長較長的寄存器,反之則不行。
大家會發現,8個通用寄存器實在無法滿足編程的要求。爲了解決這一矛盾,引入了堆棧這一聰明的設想。你可以把堆棧想象爲一塊放箱子的區域,用入棧(PUSH)可將一個箱子放在現有箱子的最頂端,而出棧(POP)可將現有箱子最頂端的那個箱子取出。看看下面的指令吧:
push eax //eax進棧, 堆棧爲eax
push ebx //eax進棧, 堆棧爲eax ebx
push ecx //eax進棧, 堆棧爲eax ebx ecx
pop ebx //ebx=ecx, 堆棧爲eax ebx
pop eax //eax=ebx, 堆棧爲eax
pop ecx //ecx=eax, 堆棧空
可以看到,堆棧不僅可以方便地暫時存儲數據而且還可以調整他們的次序。

 

下面看一個彙編的綜合運用:冒泡排序。

#include <iostream>
using namespace std;

#define array_size 10

int a[array_size]={42, 73, 65, 97, 23, 59, 18, 84, 36, 6};

void main()
{
 int *p;
 p=&a[0];
 p--;
 
 __asm
 {
  mov esi,p;
  mov ecx,array_size;
_outloop:
  mov edx,ecx;
_inloop:
  mov eax, [ esi+ecx*4 ]; //一個int佔4字節
  mov ebx, [ esi+edx*4 ];
  cmp eax, ebx;
  jnb _noxchg; //不交換
  mov [ esi+ecx*4 ], ebx;
  mov [ esi+edx*4 ], eax;
_noxchg:
  dec edx;
  jnz _inloop;
  loop _outloop;
 }
 
 for (int i=0;i<10;i++)
  cout<<a[i]<<" "<<endl;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章