在高级语言中,我们可以无所顾忌地使用各种语句,再由编译器将语句经过非常复杂的编译过程将其转换为机器指令后运行。事实上,处理器本身所能处理的指令不多;更糟糕的是,大部分指令不能直接施用在内存中的变量上,要借助寄存器这个中间存储单元(你可以把寄存器看做是一个变量)。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;
}