在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; /* 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
}
        注意内联汇编无法调用重载函数,因为被重载函数名与原函数名不一样。所以如果你需求调用的话, (我记得vcbase中有关于重载函数的文章),就不要定义重载函数,且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;
}

_CodeOfAsm:  
                      ;   这里是代码  
                      _EMIT       0x90         ;   NOP指令  
              }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值