汇编语言直接偏移量操作数

变量名加上一个位移就形成了一个直接 - 偏移量操作数。这样可以访问那些没有显式标记的内存位置。假设现有一个字节数组 arrayB:

arrayB BYTE 10h,20h,30h,40h,50h

用该数组作为 MOV 指令的源操作数,则自动传送数组的第一个字节:

mov al,arrayB          ;AL = 10h

通过在 arrayB 偏移量上加 1 就可以访问该数组的第二个字节:

mov al,[arrayB+1]            ;AL = 20h

如果加 2 就可以访问该数组的第三个字节:

mov al, [arrayB+2]           ;AL = 30h

形如 arrayB+1 一样的表达式通过在变量偏移量上加常数来形成所谓的有效地址。有效地址外面的括号表明,通过解析这个表达式就可以得到该内存地址指示的内容。汇编器并不要求在地址表达式之外加括号,但为了清晰明了,建议使用括号。

MASM 没有内置的有效地址范围检查。在下面的例子中,假设数组 arrayB 有 5 个字节,而指令访问的是该数组范围之外的一个内存字节。其结果是一种难以发现的逻辑错误,因此,在检查数组引用时要非常小心:

mov al, [arrayB+20]               ; AL = ??

字和双字数组

在 16 位的字数组中,每个数组元素的偏移量比前一个多 2 个字节。这就是为什么在下面的例子中,数组 ArrayW 加 2 才能指向该数组的第二个元素:

.data
arrayW WORD 100h,200h,300h
.code
mov ax, arrayW ;AX = 100h
mov ax,[arrayW+2] ;AX = 200h

同样,如果是双字数组,则第一个元素偏移量加 4 才能指向第二个元素:

.data
arrayD DWORD l0000h,20000h
.code
mov eax, arrayD ;EAX = 10000h
mov eax,[arrayD+4] ;EAX = 20000h

汇编语言OFFSET运算符:返回数据标号的偏移量

OFFSET 运算符返回数据标号的偏移量。这个偏移量按字节计算,表示的是该数据标号距离数据段起始地址的距离。如下图所示为数据段内名为 myByte 的变量。

OFFSET 示例

在下面的例子中,将用到如下三种类型的变量:

.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal2 DWORD ?

假设 bVal 在偏移量为 0040 4000(十六进制)的位置,则 OFFSET 运算符返回值如下:

mov esi,OFFSET bVal ; ESI = 00404000h
mov esi,OFFSET wVal ; ESI = 00404001h
mov esi,OFFSET dVal ; ESI = 00404003h
mov esi,OFFSET dVal2 ; ESI = 00404007h

OFFSET 也可以应用于直接 - 偏移量操作数。设 myArray 包含 5 个 16 位的字。下面的 MOV 指令首先得到 myArray 的偏移量,然后加 4,再将形成的结果地址直接传送给 ESI。因此,现在可以说 ESI 指向数组中的第 3 个整数。

.data
myArray WORD 1,2,3,4,5
.code
mov esi,OFFSET myArray + 4

还可以用一个变量的偏移量来初始化另一个双字变量,从而有效地创建一个指针。如下例所示,pArray 就指向 bigArray 的起始地址:

.data
bigArray DWORD 500 DUP (?)
pArray DWORD bigArray

下面的指令把该指针的值加载到 ESI 中,因此,这个 ESI 寄存器就可以指向数组的起始地址:

mov esi,pArray

汇编语言ALIGN伪指令:对齐一个变量

ALIGN 伪指令将一个变量对齐到字节边界、字边界、双字边界或段落边界。

语法如下:

ALIGN bound

Bound 可取值有:1、2、4、8、16。当取值为 1 时,则下一个变量对齐于 1 字节边界(默认情况)。当取值为 2 时,则下一个变量对齐于偶数地址。当取值为 4 时,则下一个变量地址为 4 的倍数。当取值为 16 时,则下一个变量地址为 16 的倍数,即一个段落的边界。

为了满足对齐要求,汇编器会在变量前插入一个或多个空字节。为什么要对齐数据?因为,对于存储于偶地址和奇地址的数据来说,CPU 处理偶地址数据的速度要快得多。

下述例子中,bVal 处于任意位置,但其偏移量为 0040 4000。在 wVal 之前插入 ALIGN 2 伪指令,这使得 wVal 对齐于偶地址偏移量:

bVal BYTE ? ;00404000h
ALIGN 2
wVal WORD ? ;00404002h
bVal2 BYTE ? ;00404004h
ALIGN 4
dVal DWORD ? ;00404008h
dVal2 DWORD ? ;0040400Ch

请注意,dVal 的偏移量原本是 0040 4005,但是 ALIGN 4 伪指令使它的偏移量成为 0040 4008。

汇编语言PTR运算符:重写操作数的大小类型

PTR 运算符可以用来重写一个已经被声明过的操作数的大小类型。只要试图用不同于汇编器设定的大小属性来访问操作数,那么这个运算符就是必需的。

例如,假设想要将一个双字变量 myDouble 的低 16 位传送给 AX 由于操作数大小不匹配,因此,汇编器不会允许这种操作:

.data
myDouble DWORD 12345678h
.code
mov ax,myDouble

但是,使用 WORD PTR 运算符就能将低位字(5678h)送入 AX:

mov ax,WORD PTR myDouble

为什么送入 AX 的不是 1234h ?因为,x86 处理器采用的是小端存储格式,即低位字节存放于变量的起始地址。如下图所示,用三种方式表示 myDouble 的内存布局:第一列是一个双字,第二列是两个字(5678h、1234h),第三列是四个字节(78h、56h、34h、12h)。

不论该变量是如何定义的,都可以用三种方法中的任何一种来访问内存。比如,如果 myDouble 的偏移量为 0000,则以这个偏移量为首地址存放的 16 位值是 5678h。同时也可以检索到 1234h,其字地址为 myDouble+2,指令如下:

mov ax,WORD PTR [myDouble+2] ; 1234h

同样,用 BYTE PTR 运算符能够把 myDouble 的单个字节传送到 BL:

mov b1,BYTE PTR myDouble ; 78h

注意,PTR 必须与一个标准汇编数据类型一起使用,这些类型包括:BYTE、SEYTE、WORD、SWORD、DWORD、SDWORD、FWORD、QWORD 或 TBYTE。

将较小的值送入较大的目的操作数

程序可能需要将两个较小的值送入一个较大的目的操作数。如下例所示,第一个字复制到 EAX 的低半部分,第二个字复制到高半部分。而 DWORD PTR 运算符能实现这种操作:

.data
wordList WORD 5678h,1234h
.code
mov eax, DWORD PTR wordList ; EAX = 12345678h

汇编语言TYPE运算符:返回变量的大小

TYPE 运算符返回变量单个元素的大小,这个大小是以字节为单位计算的。比如,TYPE 为字节,返回值是 1;TYPE 为字,返回值是 2;TYPE 为双字,返回值是 4;TYPE 为四字,返回值是 8。示例如下:

.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?

下表是每个 TYPE 表达式的值。

表达式 表达式
TYPE var1 1 TYPE var3 4
TYPE var2 2 TYPE var4 8

汇编语言LENGTHOF运算符:计算数组中元素的个数

LENGTHOF 运算符计算数组中元素的个数,元素个数是由数组标号同一行出现的数值来定义的。示例如下:

.data
byte1 BYTE 10,20,30
array1 WORD 30 DUP (?),0,0
array2 WORD 5 DUP(3 DUP(?))
array3 DWORD 1,2,3,4
digitStr BYTE "12345678",0

如果数组定义中出现了嵌套的 DUP 运算符,那么 LENGTHOF 返回的是两个数值的乘积。下表列出了每个 LENGTHOF 表达式返回的数值。

表达式 表达式
LENGTHOF byte1 3 LENGTHOF array3 4
LENGTHOF array1 30+2 LENGTHOF digitStr 9
LENGTHOF array2 5*3    

如果数组定义占据了多个程序行,那么 LENGTHOF 只针对第一行定义的数据。比如有如下数据,则 LENGTHOF myArray 返回值为 5 :

myArray BYTE 10,20,30,40,50
BYTE 60,70,80,90,100

另外,也可以在第一行结尾处用逗号,并在下一行继续进行数组初始化。若有如下数据定义, LENGTHOF myArray 返回值为 10:

myArray BYTE 10,20,30,40,50,
60,70,80,90,100

汇编语言LABEL伪指令

LABEL 伪指令可以插入一个标号,并定义它的大小属性,但是不为这个标号分配存储空间。LABEL 中可以使用所有的标准大小属性,如 BYTE、WORD、DWORD、QWORD 或 TBYTE。
LABEL 常见的用法是,为数据段中定义的下一个变量提供不同的名称和大小属性。如下例所示,在变量 val32 前定义了一个变量,名称为 val16 属性为 WORD:

.data
val16 LABEL WORD
val32 DWORD 12345678h
.code
mov ax,val16 ; AX = 5678h
mov dx,[val16+2] ; DX = 1234h

val16 与 val32 共享同一个内存位置。LABEL 伪指令自身不分配内存。
有时需要用两个较小的整数组成一个较大的整数,如下例所示,两个 16 位变量组成一个 32 位变量并加载到 EAX 中:

.data
LongValue LABEL DWORD
val1 WORD 5678h
val2 WORD 1234h
.code
mov eax,LongValue ; EAX = 12345678h

汇编语言间接寻址

直接寻址很少用于数组处理,因为,用常数偏移量来寻址多个数组元素时,直接寻址不实用。反之,会用寄存器作为指针(称为间接寻址)并控制该寄存器的值。如果一个操作数使用的是间接寻址,就称之为间接操作数。

间接操作数

保护模式

任何一个 32 位通用寄存器(EAX、EBX、ECX、EDX、ESI、EDI、EBP 和 ESP)加上括号就能构成一个间接操作数。

寄存器中存放的是数据的地址。示例如下,ESI 存放的是 byteVal 的偏移量,MOV 指令使用间接操作数作为源操作数,解析 ESI 中的偏移量,并将一个字节送入 AL:

.data
byteVal BYTE 10h
.code
mov esi,OFFSET byteVal
mov al,[esi] ; AL = 10h

如果目的操作数也是间接操作数,那么新值将存入由寄存器提供地址的内存位置。在下面的例子中,BL 寄存器的内容复制到 ESI 寻址的内存地址中:

mov [esi],bl

PTR 与间接操作数一起使用

一个操作数的大小可能无法从指令中直接看出来。下面的指令会导致汇编器产生“operand must have size(操作数必须有大小)”的错误信息:

inc [esi] ;错误:operand must have size

汇编器不知道 ESI 指针的类型是字节、字、双字,还是其他的类型。而 PTR 运算符则可以确定操作数的大小类型:

inc BYTE PTR [esi]

数组

间接操作数是步进遍历数组的理想工具。下例中,arrayB 有 3 个字节,随着 ESI 不断加 1,它就能顺序指向每一个字节:

.data
arrayB BYTE 10h,20h,30h
.code
mov esi,OFFSET arrayB
mov alz [esi] ;AL = lOh
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 axz [esi] ; AX = 3000h

假设 arrayW 的偏移量为 10200h,下图展示的是 ESI 初始值相对数组数据的位置。

示例:32 位整数相加下面的代码示例实现的是 3 个双字相加。由于双字是 4 个字节的,因此,ESI 要加 4 才能顺序指向每个数组数值:

.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] ;(第三个数)

假设 arrayD 的偏移量为 10200h。下图展示的是 ESI 初始值相对数组数据的位置:

变址操作数

变址操作数是指,在寄存器上加上常数产生一个有效地址。每个 32 位通用寄存器都可以用作变址寄存器。MASM 可以用不同的符号来表示变址操作数(括号是表示符号的一部分):

constant [reg]
[constant + reg]

第一种形式是变量名加上寄存器。变量名由汇编器转换为常数,代表的是该变量的偏移量。下面给岀的是两种符号形式的例子:

arrayB[esi] [arrayB + esi]
arrayD[ebx] [arrayD + ebx]

变址操作数非常适合于数组处理。在访问第一个数组元素之前,变址寄存器需要初始化为 0:

.data
arrayB BYTE 10h,20h,30h
.code
mov esi, 0
mov al, arrayB[esi] ; AL = 10h

最后一条语句将 ESI 和 arrayB 的偏移量相加,表达式 [arrayB+ESI] 产生的地址被解析,并将相应内存字节的内容复制到AL。
增加位移量变址寻址的第二种形式是寄存器加上常数偏移量。变址寄存器保存数组或结构的基址,常数标识各个数组元素的偏移量。下例展示了在一个 16 位字数组中如何使用这种形式:

.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]

如果有间接操作数,则要避免使用 BP 寄存器,除非是寻址堆栈数据。

变址操作数中的比例因子

在计算偏移量时,变址操作数必须考虑每个数组元素的大小。比如下例中的双字数组,下标(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 设计师希望能让编译器编写者的常用操作更容易,因此,他们提供了一种计算偏移量的方法,即使用比例因子。比例因子是数组元素的大小(字 = 2,双字 =4,四字 =8)。现在对刚才的例子进行修改,将数组下标(3)送入 ESI,然后 ESI 乘以双字的比例因子(4):

.data
arrayD DWORD 1,2,3,4
.code
mov esi, 3 ;下标
mov eax,arrayD[esi*4] ;EAX = 4

TYPE 运算符能让变址更加灵活,它可以让 arrayD 在以后重新定义为别的类型:

mov esi, 3 ;下标
mov eax,arrayD[esi*TYPE arrayD] ;EAX = 4

指针

如果一个变量包含另一个变量的地址,则该变量称为指针。指针是控制数组和数据结构的重要工具,因为,它包含的地址在运行时是可以修改的。比如,可以使用系统调用来分配(保留)一个内存块,再把这个块的地址保存在一个变量中。

指针的大小受处理器当前模式(32位或64位)的影响。下例为 32 位的代码,ptrB 包含了 arrayB 的偏移量:

.data
arrayB byte 10h,20h,30h,40h
ptrB dword arrayB

还可以用 OFFSET 运算符来定义 ptrB,从而使得这种关系更加明确:

ptrB dword OFFSET arrayB

32 位模式程序使用的是近指针,因此,它们保存在双字变量中。这里有两个例子:ptrB 包含 arrayB 的偏移量,ptrW 包含 arrayW 的偏移量:

arrayB BYTE 10h,20h,30h,40h
arrayW WORD 1000h,2000h,3000h
ptrB DWORD arrayB
ptrW DWORD arrayW

同样,也还可以用 OFFSET 运算符使这种关系更加明确:

ptrB DWORD OFFSET arrayB
ptrW DWORD OFFSET arrayW

高级语言刻意隐藏了指针的物理细节,这是因为机器结构不同,指针的实现也有差异。汇编语言中,由于面对的是单一实现,因此是在物理层上检查和使用指针。这样有助于消除围绕着指针的一些神秘感。

使用 TYPEDEF 运算符

TYPEDEF 运算符可以创建用户定义类型,这些类型包含了定义变量时内置类型的所有状态。它是创建指针变量的理想工具。比如,下面声明创建的一个新数据类型 PBYTE 就是一个字节指针:

PBYTE TYPEDEF PTR BYTE

这个声明通常放在靠近程序开始的地方,在数据段之前。然后,变量就可以用 PBYTE 来定义:

.data
arrayB BYTE 10h,20h,30h,40h
ptr1 PBYTE ? ;未初始化
ptr2 PBYTE arrayB ;指向一个数组

示例程序:Pointers

下面的程序(pointers.asm)用 TYPEDEF 创建了 3 个指针类型(PBYTE、PWORD、PDWORD)。此外,程序还创建了几个指针,分配了一些数组偏移量,并解析了这些指针:


TITLE Pointers (Pointers.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword
;创建用户定义类型
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
invoke ExitProcess,0
main ENDP
END main

汇编相关运算符和指令相关推荐

  1. dec-c 怎么改语言,汇编中的dec指令是什么指令

    公告: 为响应国家净网行动,部分内容已经删除,感谢读者理解. 话题:汇编中的dec指令是什么指令?回答:相当于c语言中的自减.如:dec R0就是说R0=R0-1.如果R0=5,那么运算过后,R0=4 ...

  2. linux汇编语言cmp,汇编语言中cmp指令用法笔记与总结

    这篇文章主要介绍了汇编语言中cmp指令用法,结合实例形式总结分析了汇编语言cmp指令基本功能.使用方法及操作注意事项,需要的朋友可以参考下 本文实例讲述了汇编语言中cmp指令用法.分享给大家供大家参考 ...

  3. 字符按位取反python_汇编 (NOT)按位取反指令

    知识点:  (NOT)按位取反指令  逻辑取反(!)  按位取反(~)  SETZ(SETE) 取ZF位值保存  SETNZ(SETNE)将ZF位值取反后保存 一.逻辑取反(!) !111 ...

  4. C++ 自定义string类 重载相关运算符

    c++提供的string类 c++提供的string类可以做很多事情,它本质上是也是类,它的很多运算符都是通过重载进行实现的. 今天我们自己来简单来实现MyString类来模拟 string类能做得一 ...

  5. 汇编语言中xor指令_汇编各类指令用法及含义分析 - 全文

    什么是汇编语言 汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指 ...

  6. debug和汇编编译器masm对指令不同的处理

    debug和汇编编译器masm对指令不同的处理 1.在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用[...]来表示内存单元.如果 在[  ]里用一个常量idata直接给出内存单元的偏移地 ...

  7. 研究16位汇编NEG求补指令

    转自: http://bbs.pediy.com/showthread.php?t=81209 [文章标题]: 研究16位汇编NEG求补指令  [文章作者]:  hzmslx 我是个菜鸟··学16位汇 ...

  8. 汇编中求补指令NEG的小问题..

    今天上汇编课,讲到快下课的时候介绍求补指令NEG...大意为:NEG指令的操作是用0减去操作数,结果送回该操作数所在地址.然后teacher又说"大家别忘啦,求补要加1啦~balabalab ...

  9. 汇编学习--7.16--int指令

    int指令 中断信息可以来自CPU的内部和外部,当CPU的内部有需要处理的事情发生的时候,将产生需要马上处理的中断信息,引发中断过程. 上一章讲解了0号中断和单步中断,这一章将讲解另一种重要的内中断, ...

最新文章

  1. 三年级优秀书籍推荐_西关小学书画比赛优秀作品展 绘画组(四)
  2. 让你工作变得更加有趣
  3. Android开发--Wifi的操作
  4. texstudio 使用方法_简单说说LaTex(TexStudio中的使用)
  5. 浅谈基于软交换的下一代网络技术
  6. 【转】CCScale9Sprite和CCControlButton
  7. Java的tomcat的配置_java-Tomcat配置
  8. 漂亮的英文字体 android,资深字体设计师私藏的10大APP,只怪你知道的太晚 !
  9. iReport 生成pdf分页问题
  10. 「读书笔记」余生,请多指教
  11. 【R语言】字符串处理
  12. 时间转换 秒(s)转 ()天 ()小时() 分钟 ()秒
  13. protoc-gen-go 安装
  14. quorum中的BFT
  15. android7 ios,碰到这样的安卓7.0系统,iOS10真要吓出一身汗!
  16. NYOJ 171 聪明的聪聪
  17. 打开edge时有概率打开抖音,芒果TV如何解决?
  18. 弦线上的波动方程推导
  19. 拼多多获得搜索词推荐 API
  20. 万龙机器人_星辰血途-108.百年仇恨(4)万龙朝苍-爱阅小说网

热门文章

  1. msf生成linux elf木马反弹shell
  2. 项目管理信息系统效益分析报告
  3. 支持360打倒无耻的tx
  4. 计算机的CPU检测软件
  5. 关于添加SATA驱动的回复(XP安装时分两个阶段)
  6. 笔记本电脑键盘无法使用的参考解决方案
  7. C标准库“圣经”:C标准库
  8. Javascript 浏览器检测
  9. C6—Qt+mplayer+win10实现视频播放器
  10. php学生选课系统mysql