天天看點

在Visual C++中使用内聯彙編

 在Visual   C++中使用内聯彙編  

内聯彙編的優缺點  

      因為在Visual   C++中使用内聯彙編不需要額外的編譯器和聯接器,且可以處理Visual   C++中不能處理的一些事情,而且可以使用在C/C++中的變量,是以非常友善。内聯彙編主要用于如下場合:  

      1.使用彙編語言寫函數;  

      2.對速度要求非常高的代碼;  

      3.裝置驅動程式中直接通路硬體;  

      4. "Naked "   Call的初始化和結束代碼。  

          //(. "Naked ",了解了意思,但是不知道怎麼翻譯^_^,大概就是不需要C/C++的編譯器(自作聰明)生成的函數初始化和收尾代碼,請參看MSDN的 "Naked   Functions "的說明)  

      内聯彙編代碼不易于移植,如果你的程式打算在不同類型的機器(比如x86和Alpha)上運作,應當盡量避免使用内聯彙編。這時候你可以使用MASM,因為MASM支援更友善的的宏指令和資料訓示符。  

VC内聯彙編

一 内聯彙編關鍵字

   在Visual C++使用内聯彙編用到的是__asm關鍵字,這個關鍵字有兩種使用方法:

  1.簡單__asm塊

__asm

{

   MOV AL, 2

   MOV DX, 0XD007

   OUT AL, DX

}

 2.在每條彙編指令之前加__asm關鍵

__asm MOV AL, 2

__asm MOV DX, 0xD007

__asm OUT AL, DX

        因為__asm關鍵字是語句分隔符,是以你可以把彙編指令放在同一行:

   __asm MOV AL, 2 __asm MOV DX, 0XD007 __asm OUT AL, DX

   顯然,第一種方法和C/C++的風格很一緻,并且有很多其它優點,是以推薦使用第一種方法。

   不象在C/C++中的"{}",__asm塊的"{}"不會影響C/C++變量的作用範圍。同時,__asm塊可以嵌套,嵌套也不會影響變量的作用範圍。

二 在__asm塊中使用彙編語言

  1.内聯彙編指令集

   内聯彙編完全支援的Intel 486指令集,允許使用MMX指令。不支援的指令可以使用_EMIT僞指令定義(_EMIT僞指令說明見下文)。

  2.MASM表達式

   内聯彙編可以使用MASM中的表達式。比如: MOV EAX, 1。

  3.資料訓示符和操作符

   雖然__asm塊中允許使用C/C++的資料類型和對象,但它不能用MASM訓示符和操作符定義資料對象。這裡特别指出,__asm塊中不允許

MASM中的定義訓示符: DB、DW、DD、DQ、DT和DF,也不允許DUP和THIS操作符。MASM結構和記錄也不再有效,内聯彙編不接受STRUC、RECORD、

WIDTH或者MASK。

  4.EVEN和ALIGN訓示符

   盡管内聯彙編不支援大多數MASM訓示符,但它支援EVEN和ALIGN,當需要的時候,這些訓示符在彙編代碼裡面加入NOP(空操作)指令使

标号對齊到特定邊界。這樣可以使某些處理器取指令時具有更高的效率。

  5.MASM宏訓示符

   内聯彙編不是宏彙編,不能使用MASM宏訓示符(MACRO、REPT、IRC、IRP和ENDM)和宏操作符(<>、!、&、%和.TYPE)。

    6.段說明

  必須使用寄存器來說明段,跨越段必須顯式地說明,如ES:[BX]。

  7.類型和變量大小

   我們可以使用LENGTH來取得C/C++中的數組中的元素個數,如果不是一個數組,則結果為一。使用SIZE來取得C/C++中變量的大小,一

個變量的大小是LENGTH和TYPE的乘積。TYPE用來取得一個變量的大小,如果是一個數組,它得到的一個數組中的單個元素的大小。

  8.注釋

   可以使用C/C++的注釋(//,),但推薦用ASM的注釋,即";"号。

  9._EMIT僞指令

   _EMIT僞指令相當于MASM中的DB,但一次隻能定義一個位元組,比如:

__asm

{

   JMP _CodeOfAsm

   _EMIT 0x00 ; 定義混合在代碼段的資料

   _EMIT 0x01

   _CodeOfAsm:

   ; 這裡是代碼

   _EMIT 0x90 ; NOP指令

}

三 在内聯彙編代碼中使用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;

   goto c_dest;

   goto A_Dest;

   goto a_dest;

   __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:      

   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

}

        注意内聯彙編無法調用重載函數,因為被重載函數名與原函數名不一樣。是以如果你需求調用的話, (我記得vcbase中有關于重載函數的文章),就不要定義重載函數,且C++函數必須使用extern "C"關鍵字來定義。

因為C中的預處理指令#define是字元代換,是以你可以使用#define來定義一個彙編宏,例如:

#define PORTIO __asm      \

         \

{                         \

   __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;

}

_CodeOfAsm:  

                      ;   這裡是代碼  

                      _EMIT       0x90         ;   NOP指令  

              }