程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 內聯匯編基礎知識

內聯匯編基礎知識

編輯:匯編語言

幾天看了孫原等幾位仁兄關於匯編語言的幾篇文章,頗感興趣。於是查了查98版的MSDN中,其中也有幾篇關於內聯匯編的基礎,索引字是asm。講得不算太難,於是試著將其內容寫下來了,特此貼來。

一、內聯匯編簡述

Visual C++ 6.0編譯器下,內聯匯編可以使用所有的Intel486處理器指令集。而且可以對目標處理器建立起偽指令來實現附加指令功能。內聯匯編可以使用MASM編譯器所允許的表達式,其中的一些表達式可以通過操作符和操作數的組合,對單精值進行運算.

雖然內聯匯編可以訪問C\C++中的數據變量以及類對象,但它不可能通過MASM指令和操作符來定義數據及對象。尤其你還不能使用DB, DW, DD, DQ, DT和DF等定義指令以及DUP和This操作符。匯編中的結構記錄也不是可用的。內聯匯編也不支持directives STRUC, RECORD, WIDTH, 和 MASK指令。不過,在內聯匯編可以使用到一個_emit宏指令,它類似於MASM中的DB指令,它可以在本區域內定義出一個字節型,雖然它每次只能定義一個字節出來,但還是可以用它來定義出一個字符串,請看示例:

#define randasm __asm _emit 0x4A __asm _emit 0x43 __asm _emit 0x4B

__asm
{
   randasm
}

雖然內聯匯編不支持MASM中的很多指令,但它支持EVEN 和 ALIGN指令。它們被用於那些需要使用align labels來指定分界線的匯編指令。

內聯匯編不可以是一個宏匯編程序,你不可以使用MASM中的宏定義指令以及宏操作符。但內聯匯編是可以使用C\C++中的預理指令來定義宏。

在處理段時,你只能使用寄存器,而不是通過名字,因為在內聯匯編中這是非法的。而且段必須顯式地使用寄存器,如: ES:[BX]

在內聯匯編使用操作符LENGTH, SIZE, 和 TYPE可以來對變量以及類型進行長度的測量,你可以使用它們來求得C\C++中的變量及類型的長度:

*LENGTH操作符可以返回在一個變量數組中的元素個數,如果返回為1則代表這不是一個變量數組。

*SIZE操作符可以求得一個變量及類型的總長度。這個值也可以由LENGTH與TYPE積來求得。

*TYPE操作符可以求得一個變量及類型的長度,與SIZE不同的是,如果變量名是一個數組的話,則返加這個數組中單個元素的長度。

具體情況請看下表:

__asm C Size LENGTH arr sizeof(arr)/sizeof(arr[0]) 8 SIZE arr sizeof(arr) 16 TYPE arr sizeof(arr[0]) 2

包含著內聯匯編的程序可以使用/Zi選項編譯,從而來進行代碼級的調試工作。這樣,你就可以同時在C\C++程序段與內聯匯編段中設置斷點,如果你使用/Fas選項允許混合匯編與C\C++源程序調試方式,那麼你就可以看到混合著匯編與源程序的代碼集合。

Visual C++編譯器允許你在內聯匯編程序中使用Intel處理器的MMX指令集。不過如果使用MMX指令集編譯器會發生警告。更多的信息請查看MSDN的Web站點。

二、關於內聯匯編的具體使用說明:

因為內聯匯編不需要編譯與鏈接過程,因此它比一個匯編程序更為方便。由於它能夠訪問C\C++中的變量及函數,所以它能夠更好和你C\C++代碼融為一體。內聯匯編可以在以下方面進行編程:

*用匯編語言編寫函數。

*使用匯編語言來產生速度最優化代碼段。

*直接使用匯編語言來對硬件進行訪問。

*為naked函數調用編寫保護現場和恢復現場代碼( prolog and epilog code)

如果你計劃在不同機器上運行程序的話,那麼你應該分別放置不同機種的專一匯編代碼。因為內聯匯編有一定的機器專一性,它不完全支持所有MASM中的宏與數據類型。

VC不支持C++中的asm關鍵字,所以你需要使用__asm(兩個下劃線)關鍵字來編寫你的內聯匯編代碼。

你可以使用這個關鍵字來編寫一個內聯代碼段,如:

__asm
{
  mov al, 2
  mov dx, 0xD007
  out al, dx
}

也可以只編寫一行式的內聯代碼,如:

__asm mov al, 2
__asm mov dx, 0xD007
__asm out al, dx

以上兩段代碼是同義的。但是第一種寫法比較有好處(advantages?),它可以與C源碼明顯的區別開來,而且避免重復輸入不必要__asm關鍵字。 在內聯匯編代碼段中以下的C語言元素是可以被應用的:

*符號,包括跳轉標簽,變量名和函數名。(所使用C\C++符號必須在其使用名域之內。)

*C\C++常量,包括const符號化常量和共用體(enum)中的常量

*宏以及預處理表達式。

*C\C++風格的注釋,//,/*,*/

*類型名

*typedef定義的類型名

三、在內聯匯編代碼中使用C操作符

在內聯匯編中不能使用C\C++專有的操作符,諸如:<<,雖然,有一些操作符是MASM與C中都在使用的,比如:*操作符。但在內聯匯編中被優先解釋為匯編操作符。例如,在C中方括號是用來訪問數組的元素的。C將它解釋為首地址+單個元素長度*元素序號。而在內聯匯編中,則將它解釋為首地址+方括號中定義的數量。是對一個地址的字節偏移量。這一點是在編程中應該特注意的。所以以下這一段代碼是錯誤的

int array[10];
__asm mov array[6], 0 ; 期望達到C中的array[6] = 0功能,但這是錯誤的。

正確的代碼如下:

__asm mov array[6 * TYPE int], 0 ;
array[6] = 0;

在內聯匯編中使用C\C++符號(如前面如述,符號包括常量名,變量名,函數名以及跳轉標簽)應注意以下幾點:

*所使用C\C++符號必須在其使用名域之內。

*一般的情況下,一句匯編語句只允許出現一個C\C++符號。在LENGTH, TYPE, 和 SIZE表達式中則可以使用多個C\C++符號。

*就像C語言一樣,在內聯匯編中調用函數之前,必須顯式的聲明函數。否則編譯器將會報錯。

*注意在內聯匯編中不能使用那些與MASM中保留字相同的C\C++符號。

*注意C\C++中的類,結構體以及共用體在內聯匯編中不直接使用。

下面將舉幾個關於使用C\C++符號的例子。

如果先前C已經定義了一個變量var,那麼則內聯匯編可以訪問這個變量如下:

__asm mov eax, var   ;將變量var中的值賦給eax寄存器中。

如果有一個結構體first_type和一個實例hal:

struct first_type
{
  char *weasel;
  int same_name;
} hal;

在訪問hal對象時,則必須如下:

__asm
{
  mov ebx, OFFSET hal       ;取得hal對象的首地址
  mov ecx, [ebx]hal.same_name ;加上same_name偏移值,則可以訪問到成員same_name
  mov esi, [ebx]hal.weasel  ;加上weasel偏移值。
}

下面是一個內聯匯編如何實現一個函數的例子:

#include <stdio.h>
int power2( int num, int power );
void main( void )
{
  printf( "3 times 2 to the power of 5 is %d\n", \
      power2( 3, 5) );
}
int power2( int num, int power )
{
  __asm
  {
   mov eax, num  ; 取得第一個參數
   mov ecx, power ; 取得第二個參數
   shl eax, cl   ; EAX = EAX * CL
  }
  //在函數中,返回值是由eax負責往回傳遞的。(順便問一句ax與eax有什麼不同啊?是不是一樣的?)
}

因為內聯函數中沒有return,所以在上面的例子中,編譯器會報出警告。還好,不像Java一樣,少一個多一個return都會編譯不通過。你可以使用宏#pragma warning來關掉警告器。在pascall式函數中堆棧的復位是由函數負責的,而不是調用者。在上面的例子中,由是在C函數中內部嵌入匯編來完成匯編函數的。在C函數出口處,C編譯器會自動添加復棧指令,而不必自己添寫。那反而會使系統混亂. 在內聯匯編中跳轉指令(包括條件跳轉),可以跳轉到C語言goto能到的所有地方。Goto也可以跳到內聯匯編中定義的標簽,示例如下:

void func( void )
{
  goto C_Dest; /* Legal: correct case  */
  goto c_dest; /* Error: incorrect case在C中大小寫區分。*/
  goto A_Dest; /* Legal: correct case  */
  goto a_dest; /* Legal: incorrect case */
  __asm
  {
   jmp C_Dest ; Legal: correct case
   jmp c_dest ; Legal: incorrect case
   jmp A_Dest ; Legal: correct case
   jmp a_dest ; Legal: incorrect case
   a_dest:  ; __asm label
  }
  C_Dest:    /* C label */
  return;
}

另外,在給標簽起名時盡量避免與C內部的或已經使用了的標簽名重名,如果那樣的將會出現災難性的程序錯誤。因此,在起名時最好追查一下是否這個名字已經被使用了。在引用函數時,應注意參數的從右向左方向地壓棧。比如有一個函數是 int CAdd (int a,int b) 則應該如此調用:

__asm
{
    mov eax,2;
    push; 參數b等於2
    mov eax,3;
    push; 參數a等於3
    call CAdd;調用CAdd函數
    mov Result,eax;所有函數的返回值都被存放在eax。於是,Result等於5
}

注意內聯匯編無法調用重載函數,因為被重載函數名與原函數名不一樣。所以如果你需求調用的話, (我記得vckbase中有關於重載函數的文章),就不要定義重載函數,且C++函數必須使用extern "C"關鍵字來定義。因為C中的預處理指令#define是字符代換,所以你可以使用#define來定義一個匯編宏,例如:

#define PORTIO __asm      \
/* Port output */         \
{                         \
   __asm mov al, 2        \
   __asm mov dx, 0xD007   \
   __asm out al, dx       \
}

以上,就是內聯匯編的基本使用描述。由於,本人的英文並不是太好,所以寫出來的文章有些不連續,而且大部分話是我自己說的,或許還會譯錯的地方,還請大家指教見諒。以下是我自己寫的一段關於類,結構體的示例:

#include <iostream.h>
struct MyData
{
    int nMember1;
    int * lpMember2;
};
void main()
{
    MyData sample;
    __asm//這是對成員變量賦值
    {
         mov eax,12;
         mov sample.nMember1,eax;
    }
    cout <<sample.nMember1<<endl;
    __asm//這是對成員指針賦值
    {
         lea eax,sample.nMember1;
         mov sample.lpMember2,eax;
    }
    cout <<*sample.lpMember2<<endl;
   
    __asm//這是對指針所指向的變量賦值
    {
         mov ebx,sample.lpMember2;
         mov eax,5;
         mov [ebx],eax;
    }
    cout <<sample.nMember1<<endl;
}

不過,對於成員函數的調用仍沒有成功。請各位高手幫忙解決這個問題。謝謝。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved