x86汇编 间接寻址的方法

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

   结束了。谢谢点评。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值