VC内嵌汇编基础知识

http://www.cppblog.com/xingkongyun/archive/2008/12/21/70003.html

为了加速游戏,一提起汇编语言,大家也许会感到很神秘。其实如果你学起来就会发现,它并非想象中那样难。特别是内嵌汇编,由于它和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   <iostream>   
  using   namespace   std;   
  //此程序也展示了内嵌汇编应如何使用C++中的指针   
  void   main(   )   
  {   
  unsigned   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;   
  }   
  第三条指令是交换指令,形式为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,   堆栈空   
  可以看到,堆栈不仅可以方便地暂时存储数据而且还可以调整他们的次序。   
    
  6.3   算术指令   
  算术指令大都影响标志寄存器。这些指令比较容易明白,现在将其列出:   
  表6.1   
  clc CF=0   
  stc CF=1   
  cmc CF=1-CF   
  add   a,b a=a+b     (结果过大可能会有古怪的结果,且置CF   1)   
  adc   a,b a=a+b+CF   (加上进位)   
  sub   a,b a=a-b       (如结果小于0会加上2的16或32次方,且置CF   1)   
  sbb   a,b a=a-b-CF     (减去退位)   
  inc   a a++   
  dec   a a-   -   
  neg   a a=-a   
  mul   a eax=eax*a后的低32位,   edx=高32位例: mov   eax,234723                 mov   edx,   12912189               mul   edx;                 则eax=2835794967       edx=705   
  div   a eax=(edx   eax)/a的商,   edx=余数例: mov   eax,12121 mov   edx,2                 此时(edx   eax)=8589946713                 mov   ebx,121                 div   ebx;                   则eax=70991295               edx=18   
  imul   /   idiv   dest,   src 有符号数乘   /   除法,dest=dest乘   /   除src   
  imul   /   idiv   dest,   s1,   s2 有符号数乘   /   除法,dest=s1乘   /   除s2   
    
  为了让大家弄懂标志,请看两段程序(出现的数都为十六进制数):   
  表6.2   
  指令 CF ZF SF OF PF AX或BX   
  mov   ax,   7896 ? ? ? ? ? 7896   
  add   al,   ah 1 0 0 0 0 780e   
  add   ah,   al 0 0 1 1 0 860e   
  add   al,   f2 1 1 0 0 1 8600   
  add   al,   1234 0 0 1 0 0 9834   
    
  mov   bx,   9048 ? ? ? ? ? 9048   
  sub   bh,   bl 0 0 0 1 1 4848   
  sub   bl,   bh 0 1 0 0 1 4800   
  sub   bl,   5 1 0 1 0 0 48fb   
  sub   bx,   8f34 1 0 1 1 0 b9c7  

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{   
  _addax:     add   ax,1;   //_addax是标号   
      jmp   _addax;   
      }   
  转移指令有:   
  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   <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]<<"   ";  

时间: 2024-10-01 15:53:08

VC内嵌汇编基础知识的相关文章

内联汇编基础知识

几天看了孙原等几位仁兄关于汇编语言的几篇文章,颇感兴趣.于是查了查98版的MSDN中,其中也有几篇关于内联汇编的基础,索引字是asm.讲得不算太难,于是试着将其内容写下来了,特此贴来. 一.内联汇编简述 Visual C++ 6.0编译器下,内联汇编可以使用所有的Intel486处理器指令集.而且可以对目标处理器建立起伪指令来实现附加指令功能.内联汇编可以使用MASM编译器所允许的表达式,其中的一些表达式可以通过操作符和操作数的组合,对单精值进行运算. 虽然内联汇编可以访问C\C++中的数据变量

内嵌汇编与C/C++实现的冒泡排序,快速排序算法排序500W个数据对比

内嵌汇编是微软为了提高程序的直接操作硬件能力,以及实现大任务程序效率的提高,而嵌入在VC中的,他不依赖于汇编器对内嵌汇编代码进行汇编,这些内嵌汇编代码由C编译器内部实现,可以使程序好像自陷到汇编状态一样.这意味着你如果在C/C++程序中使用了 inline  asm进行 汇编 那么 就注定无法跨平台的,对于没有汇编基础的同学是需要去学习下王爽的8086汇编程序设计.,因为有些时候C++程序员是不得不懂这些东西的 ,否则你永远无法知道编译器为你的函数做了什么,.你还有哪些地方需要优化, 不管处于什

GCC在C语言中内嵌汇编 asm __volatile__ 【转】

转自:http://blog.csdn.net/pbymw8iwm/article/details/8227839 在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作. 1.简单的内嵌汇编 例:        __asm__ __volatile__("hlt"); "__asm__&quo

GCC的内嵌汇编语法

1 Overview 开发一个OS,尽管绝大部分代码只需要用C/C++等高级语言就可以了,但至少和硬件相关部分的代码需要使用汇编语言,另外,由于启动部分的代码有大小限制,使用精练的汇编可以缩小目标代码的Size.另外,对于某些需要被经常调用的代码,使用汇编来写可以提高性能.所以我们必须了解汇编语言,即使你有可能并不喜欢它.     如果我们选择的OS开发工具是GCC以及GAS的话,就必须了解AT&T汇编语言语法,因为GCC/GAS只支持这种汇编语法.  本文只讨论AT&T的汇编语法,以及G

visual c++-Visual C++中内嵌汇编的问题

问题描述 Visual C++中内嵌汇编的问题 如下,是一个利用内嵌汇编实现的两整数交换的程序.输出结果是2,1;2,1;1,2;2,1.可以看到Swap2这个函数行不通,在函数内两个变量确实交换了,但是调用后a和b没有交换,仍然是2,1.就像是传值一样,而没有传址,令我很困惑. #include <cstdio> void Swap1(int &_int1, int &_int2); void Swap2(int &_int1, int &_int2); in

x64环境下,把内嵌汇编的汇编单独放在.asm文件中

问题描述 x64环境下,把内嵌汇编的汇编单独放在.asm文件中 void GDIRender::YUV_TO_RGB24(unsigned char *puc_y int stride_y unsigned char *puc_u unsigned char *puc_v int stride_uv unsigned char *puc_out int width_y int height_yint stride_out) { int y horiz_count;unsigned char *p

Linux内核系列—C语言中内嵌汇编 asm __volatile__,asm__volatile_【转】

转自:http://www.bkjia.com/Androidjc/1109412.html 在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作. 1.简单的内嵌汇编例:        __asm__ __volatile__("hlt"); "__asm__"表示后面的代码为内嵌

C语言内嵌汇编(arm-v7)----加减乘移位

在现代嵌入式操作系统中,汇编语言当然必不可少,汇编语言的优势就是执行速度快.如果在C语言的代码中,在关键的地方内嵌汇编,那么效率将会大大的提高,我们来看看代码: #include <stdio.h> int main(void) { int a = 10 ; int b = 20 ; int addsum ; int subsum ; // int mulsum ; __asm__ __volatile__ ( //传参 "mov r0 , %2 \n" //mov 执行是

vc++-VC++内联汇编要怎么将char 转换为int

问题描述 VC++内联汇编要怎么将char 转换为int 我的代码是先输入char格式..然后我选出其中的数字再转换为int格式最后算出平均值..要怎么做 解决方案 VC++中的CString.char.int类型转换VC CString,int,string,char*转换vc++中char*和CString转换 解决方案二: for(i=0; i< strlen(s); i++) { if(s[i] >= '0' && s[i] <= '9') }