1.间接操作数:
保护模式:间接操作数可以是任何用方括号括起来的任意的32位通用寄存器(EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP),寄存器里面存放着数据的偏移。
例子:
下面ESI中存放着val1的偏移地址。Mov指令使用间接操作数作为源操作数,此时ESI内的偏移地址被用来进行寻址,该地址处的一个字节被送至AL:
.data
Val1 byte 10h
.code
Mov esi,offset val1
Mov al,[esi] ;al=10h
或者如下例,间接操作数作为目的操作数,一个新值将被存放在寄存器所指向的内存位置:
Mov [esi],bl
实地址模式:实地址模式下使用16位的寄存器存放变量的偏移地址,如果要使用寄存器做间接操作数的话,只能用SI,DI,BX或BP寄存器。通常应尽量避免使用BP,因为BP常用来寻址堆栈而不是数据段。如下例:
.data
Val1 byte 10h
.code
Main proc
Startup
Mov si,offset val1
Mov al,[si] ;al=10h
2.数组:
由于间接操作符的值(寄存器内的偏移地址)可以在运行时进行修改,因此在处理数组时特别有用。与数组下标类似,间接操作数可以指向数组的不同的元素。下例中arrayB包含3个字节,我们可以递增ESI的值,使之依序指向各个字节:
.data
arrayB byte 10h,20h,30h
.code
Mov esi,offset arrayB
Mov al,[esi] ;al=10h
Inc esi
Mov al,[esi] ;al=20h
Inc esi
Mov al,[esi] ;al=30h
如果使用16位的整数数组,就需要给ESI加2以便寻址后序的各个数组元素:
.data
arrayW word 1000h,2000h,3000h
.code
Mov esi,offset arrayW
Mov ax,[esi] ;ax=1000h
Add esi,2
Mov ax,[esi] ;ax=2000h
Add esi,2
Mov ax,[esi] ;ax=3000h
例子:32位整数相加:
.data
arrayD dword 10000h,20000h,30000h
.code
Mov esi,offset arrayD
Mov eax,[esi] ;第一个数
Add esi,4
Add eax,[esi] ;第二个数
Add esi ,4
Add eax,[esi] ;第三个数
3.变址操作数
变址操作数(indexed operand)把常量和寄存器相加以得到一个有效地址,任何32位通用寄存器都可以作为变址寄存器,MASM允许使用两种不同的变址操作数格式:
Constant[reg]
[constant+reg]
第一种格式把变量的名字和寄存器结合在一起,变量的名字是代表变量偏移地址的常量。下面表示了两种格式之间的对应关系:
arrayB[esi]<---->[arrayB+esi]
arrayD[ebx]<---->[arrayD+ebx]
变址操作数用于数组处理是再合适不过了。在访问数组第一个元素之前变址寄存器应初始化为零:
.data
arrayB byte 10h,20h,30h
.code
Mov esi,0
Mov al,[arrayB+esi] ;al=10h
在上面的最后一条语句中,ESI中的值与arrayB的偏移地址相加,对表达式(arrayB+ESI)求值得到的地址被用来寻址内存中的一个字节,该字节值继而被复制到AL。
加偏移地址:另外一种变址寻址方式是把变址寄存器和常量偏移联合起来使用,不过是用变址寄存器存放数组或结构的基地址,用常量标识各个数组元素。
如下例:
.data
arrayW word 1000h,2000h,3000h
.code
Mov esi,offset arrayW
Mov ax,[esi] ;ax=1000h
Mov ax,[esi+2] ;ax=2000h
Mov ax,[esi+4] ;ax=3000h
使用16位寄存器:
在实地址模式下使用16位的寄存器作为变址操作数是很普遍的,不过这时只能使用SI,DI,BX,BP寄存器:
mov al,arrayB[si]
mov ax,arrayW[di]
mov eax,arrayD[bx]
变址操作数中的比例因子:
使用变址操作数,在计算偏移地址时必须考虑每个数组元素的大小。如下例的双字数组中,我们把下标3乘以4(双字的尺寸),以得到数组元素400h的偏移地址:
.data
arrayD dword 100h,200h,300h,400h
.code
mov esi,3* type arrayD ;arrayD[3]的偏移地址
mov eax,arrayD[esi] ; eax=400h
Intel CPU的设计者们想让编译器编写者在处理这种常见的操作时更轻松些,因此他们提供了一种使用比例因子(scale factor)计算偏移地址的寻址方式。比例因子通常是数组每个元素的大小(字的比例因子等于2,双字的比例因子等于4,八字节的比例因子等于8)。这样上例可修改如下:
.data
arrayD dword 1,2,3,4
.code
mov esi,3
mov eax,arrayD[esi*4] ;eax=400h
TYPE操作符可以使得寻址方式更加灵活,如下,arrayD将来完全可以重新定义成另外一种数据类型而下面的代码无须修改:
mov esi,3
mov eax,arrayD[esi* TYPE arrayD] ;eax=400h
4.指针
包含其他变量地址的变量称为指针变量(pointer variable)或指针(pointer),操纵数组和数据结构时指针是非常有用的,使用指针使得进行动态内存分配成为可能。基于Intel的程序使用两种基本类型的指针:NEAR和FAR,它们的尺寸受当前处理器模式的影响(16位实模式或32位保护模式),如下:
16位模式 32位模式
NEAR指针 相对数据段开始的16位偏移地址 ........32位地址偏移
FAR指针 32位的段-偏移地址 48位的段选择子-偏移地址
如下,以保护模式示例使用NEAR指针,所以它们被存储在双字变量中
arrayB byte 10h,20h,30h,40h
arrayW word 1000h,2000h,3000h
ptrB dword arrayB
ptrW dword arrayW
ptrB包含arrayB的偏移地址,ptrW包含arrayW的偏移地址。
使用OFFSET操作符使得这种关系更加清晰些:
ptrB dword offset arrayB
ptrW dword offset arrayW
高级语言有意隐藏指针的实现,因为指针的实现细节在不同的机器体系结构上是不同的。在汇编语言中,我们仅面对某种特定的体系结构上的实现,因此我们在最底层查看和使用指针,这有助于消除对于指针的神秘感。
使用TYPEDEF操作符:
TYPEDEF操作符允许创建用户自定义的类型,在定义变量时,用户自定义类型与内建类型完全相同。TYPEDEF非常适合于创建指针变量。如下声明创建了一种新的数据类型---指向字节的指针PBYTE:
PBYTE TYPEDEF PTR BYTE ;PTR操作符见下面。
.data
arrayB BYTE 10h,20h,30h,40h
ptr1 pbyte ? ;未初始化
ptr2 pbyte arrayB ;指向数组
======================================================
PTR操作符:
可以使用PTR操作符来重载操作数声明的默认尺寸,这在试图以不同于变量声明时所使用的尺寸属性来访问变量的时候非常有用。
例如,假设要将双字变量myDouble的低16位送AX寄存器,由于操作数大小不匹配,编译器将不允许下面的数据传送指令:
.data
myDouble dword 12345678h
.code
mov ax,myDouble ;错误
但是word ptr操作符使得将低字(5678h)送AX成为可能:
mov ax,word ptr myDouble
为什么不是1234h被送到AX寄存器呢?这是因为Intel CPU使用的小尾顺序存储格式有关。在下图中,我们列出了myDouble变量在内存中以三种方式显示的布局:双字,两个字(5678h,1234h)和4个字节(78h,56h,34h,12h);
双字 字 字节 偏移
123456789 5678 78 0000 myDouble
56 0001 myDouble+1
1234 34 0002 myDouble+2
12 0003 myDouble+3
CPU能够以这三种方式中的任意一种访问内存,与变量定义的方式无关。例如,如果myDouble开始于偏移0000,存储在该地址的16位值是5678h,那么还可以使用下面的语句返回地址myDouble+2处的字1234h:
mov ax,word ptr[myDouble+2] ;1234h
类似地,可以使用BYTE PTR操作符把myDouble处的一个字节送到BL:
mov bl,BYTE PTR myDouble ;78h
注意,PTR必须和汇编器的标准数据类型联合使用:BYTE,SBYTE,WORD,SWORD,DWORD,SDWORD,FWORD,
QWORD,TBYTE
将较小值送较大的目的操作数中:有时候,或许需要把内存中两个较小的值送到较大的目的操作数中。在下列中,第一个字将复制到EAX
的低半部分,第二个字将复制到EAX的高半部分,DWORD PTR操作符使这一切成为可能:
.data
wordList word 5678h,1234h
.code
mov eax,dword ptr wordList ;eax=12345678h
=======================================
关于指针的综合示例:
;创建用户自定义类型
PBYTE TYPEDEF PTR BYTE ;字节指针
PWORD TYPEDEF PTR WORD ;字指针
PDWORD TYPEDEF PTR DWORD;双字指针
.data
arrayB byte 10h,20h,30h
arrayW word 1,2,3
arrayD dword 4,5,6
;创建一些指针变量
ptr1 pbyte arrayB
ptr2 pword arrayW
ptr3 pdword arrayD
.code
main proc
;使用指针变量访问数据
mov esi,ptr1
mov al,[esi] ; 10h
mov esi,ptr2
mov ax,[esi]; 1
mov esi,ptr3
mov eax,[esi] ; 4
exit
main endp
end main
结束了。谢谢点评。