addr和offset指令的区别

本文详细对比了MASM中addr和offset伪操作符的使用场景、特点及区别,着重讨论了它们在全局与局部变量处理、引用顺序、内存分配方式等方面的不同,并提供了实例代码说明。

一、相同点

1、addr 和 offset 操作符都是获得操作数的偏移地址;
2、addr 和 offset 的处理都是先检查处理的是全局还是局部变量,若是全局变量则把其地址放到目标文件中。

二、不同点

1、addr 伪操作符,只能用在 invoke 伪指令语句中,不能用于赋值操作;
2、offset 伪操作符可以用在任何可能涉及偏移地址的指令(当然包括 invoke 伪指令)并想获取操作数偏移地址的场合中;
3、addr 不能处理向前引用(即 addr 引用的操作数必须在使用 addr 前就得定义或声明),而offset 则能(不管引用的操作数是其前或其后定义或声明);

所谓向前引用是指:标号的定义是在invoke 语句之后,比如在如下的例子:
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK //引用MsgBoxText、MsgBoxCaption 在先

......

MsgBoxCaption db "Iczelion Tutorial No.2",0 //定义或声明 MsgBoxCaption 在 addr 后
MsgBoxText db "Win32 Assembly is Great!",0 //定义或声明 MsgBoxText 在 addr 后

如果你是用addr 而不是offset 的话,那MASM就会报错

4、addr 是运行阶段在堆栈中分配内存空间,offset 是编译阶段由编译器解释。因此,addr 可以处理局部变量而 offset 则不能。

5、addr 如果检查到待处理的变量是局部变量,就在执行 invoke 语句前产生如下指令序列:

lea eax,operand
push eax

因为 lea 指令能够在运行时决定标号的有效地址,所以有了上述指令序列,就可以保证invoke的正确执行了。addr 面对全局变量时直接调用offset.

总结:为了避免出现错误,建议除在局部变量中引用 addr 操作符外,其它场合使用 offset。

说明:某些文章中对 addr 和 offset 所引用的对象仅用了“变量或标号”,我是用“操作数”来阐述的,本人的观点是:
变量或标号感觉上包含的概念过窄,比如结构、函数等等,因此,觉得使用操作数好像感觉准确些。

代码说明:

PTR: 指定要操作的数据尺寸


 
; Test1.asm
.386
.model flat, stdcall

include windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib

.data
val db 11h, 22h, 33h, 44h, 55h, 66h, 77h, 88h

.code
main proc
xor eax, eax ;清空 EAX, mov eax, 0
mov eax, dword ptr val ;
PrintHex eax ;44332211

xor eax, eax ;
mov eax, dword ptr val+1 ;
PrintHex eax ;55443322

xor eax, eax ;
mov ax, word ptr val ;
PrintHex eax ;00002211

xor eax, eax ;
mov al, byte ptr val ;
PrintHex eax ;00000011
ret
main endp
end main



OFFSET: 获取全局变量或标号的偏移地址

 
; Test2.asm
.386
.model flat, stdcall

include windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib

.data
v1 db 'abcdefg', 0
v2 dd 11223344h

.code
main proc
PrintHex offset v1 ;00403000
PrintHex offset v2 ;00403008
PrintHex offset main ;00401000 - 这里的 main 是个标号
ret
;本例中的 offset 不能用 addr 代替

main endp
end main



ADDR: 类似 offset 也是获取变量的地址...

 
; Test3.asm
.386
.model flat, stdcall

;include windows.inc

include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.data
v1 dd 00434241h ;ABC
v2 dd 00636261h ;abc

.code
main proc
invoke MessageBox, 0, offset v1, offset v2, 0 ;现在 v1v2 是全局变量
invoke MessageBox, 0, addr v2, addr v1, 0 ;使用 offset addr 均可
invoke ExitProcess, 0
main endp
end main



获取局部变量的地址只能使用 ADDR:

 
; Test4.asm
.386
.model flat, stdcall

;include windows.inc

include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.code
main proc
LOCAL v1,v2
mov v1, 00434241h
mov v2, 00636261h
;invoke MessageBox, 0, offset v1, offset v2, 0 ;offset 不能获取局部变量的地址
invoke MessageBox, 0, addr v2, addr v1, 0
invoke ExitProcess, 0
main endp
end main


THIS:

 
; Test5.asm
.386
.model flat, stdcall

include windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib

.data
TextAddr equ this byte ;伪指令 this 可让当前变量和下一个变量同址
szText db 'Asm', 0
.code
main proc
PrintHex offset szText ;00403000
PrintHex offset TextAddr ;00403000

PrintString szText ;Asm
mov [TextAddr], 'a' ;给 TextAddr 赋值
PrintString szText ;asm
ret

main endp
end main

转载自: http://genime.blog.163.com/blog/static/167157753201148103615697/

<think> 我们正在讨论ARM指令LDRB。根据引用[2],LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零(即零扩展)。该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。 指令格式: 在ARM汇编中,LDRB指令的基本格式如下: ``` LDRB{条件} 目的寄存器, [基址寄存器, 偏移量] ``` 其中: - `{条件}`是可选的执行条件,如EQ、NE等。 - `目的寄存器`:通常是通用寄存器(如R0-R12),在ARM状态下为32位,在Thumb状态下可以是32位或16位(取决于指令集)。在AArch64(ARMv8的64位执行状态)中,目标寄存器是32位的W寄存器(因为加载的是字节,并且零扩展到32位,但不会扩展到64位,所以使用W寄存器)。 - `基址寄存器`:包含内存地址的寄存器,通常是通用寄存器或栈指针(SP)。 - `偏移量`:可以是立即数(常数)或寄存器,也可以是带有移位操作的寄存器。偏移量可以是正数或负数(在立即数形式中,通常是非负的,但有些模式支持负的立即数,如后索引前索引模式)。 根据引用[3],LDRB指令的寻址模式有多种,包括: 1. 立即数偏移:`LDRB Rd, [Rn, #offset]` 2. 寄存器偏移:`LDRB Rd, [Rn, Rm]` 3. 带移位的寄存器偏移:`LDRB Rd, [Rn, Rm, LSL #n]`(但注意,由于加载的是字节,所以通常不会使用移位,因为地址偏移是以字节为单位的) 4. 前索引(自动索引):`LDRB Rd, [Rn, #offset]!` // 在加载后更新基址寄存器(Rn = Rn + offset) 5. 后索引:`LDRB Rd, [Rn], #offset` // 使用基址寄存器后,再更新基址寄存器(Rn = Rn + offset) 功能描述: LDRB指令从内存中加载一个字节(8位)。内存地址由基址寄存器(Rn)加上偏移量(立即数或寄存器)计算得到。然后,该字节被读取,放入目的寄存器(Rd)的最低8位(位7到0),而目的寄存器的高24位(位31到8)被设置为0(零扩展)。 示例: 假设寄存器R1的值为0x1000,内存地址0x1000处的字节值为0xAB,0x1001处的字节值为0xCD。 - `LDRB R0, [R1]`:从地址0x1000加载一个字节到R0。执行后,R0 = 0x000000AB。 - `LDRB R0, [R1, #1]`:从地址0x1001加载一个字节到R0。执行后,R0 = 0x000000CD。 - `LDRB R0, [R1], #1`:从地址0x1000加载一个字节到R0(R0=0x000000AB),然后将R1更新为0x1001。 - `LDRB R0, [R1, #1]!`:先将R1更新为0x1001(R1=R1+1),然后从地址0x1001加载字节到R0(R0=0x000000CD)。 注意:在AArch64中,指令格式类似,但寄存器命名不同(如W0,X1等)。例如:`LDRB W0, [X1]`。 引用说明: 根据引用[2],LDRB指令将高24位清零,即零扩展。而引用[1]提到LDR伪指令与MOV指令的立即数限制,但LDRB是真实指令,不是伪指令。引用[3]则提到了LDRB与其他加载指令(如LDR、LDRH)以及多寄存器指令(如LDM)的区别。 相关问题: 根据用户的问题,我们生成以下相关问题: §§相关问题§§ 1. LDRB指令如何实现零扩展?与LDRSB的符号扩展有何区别? 2. 在ARM汇编中,LDRB的立即数偏移范围有哪些限制?如何加载超出范围的地址? 3. LDRB指令在SDRAM存储阵列中的访问过程是怎样的?如何避免Bank冲突?[^4] </think> ### ARM指令LDRB的功能与格式分析 #### 1. **LDRB指令功能** LDRB(Load Register Byte)是ARM架构中的**字节加载指令**,核心功能如下: - **字节加载**:从内存中读取一个8位(1字节)数据 - **零扩展**:将读取的字节数据**高位补零**扩展至32位(或64位)寄存器 - 数学表达:若内存值为$B$,则寄存器值 $R = B \times 2^{24}$(32位零扩展)[^2] - **应用场景**: - 处理8位数据(如字符、像素值) - 非对齐内存访问(无需地址对齐) - 避免高位数据污染(对比LDR指令) #### 2. **指令格式详解** LDRB指令支持多种寻址模式,基本语法如下: ```assembly LDRB{<cond>} <Rd>, [<Rn>, <offset>]{!} ``` - **操作数说明**: - `<cond>`:**可选条件码**(如EQ/NE),控制条件执行 - `<Rd>`:**目标寄存器**(32位通用寄存器,如R0-R12) - `<Rn>`:**基址寄存器**(包含内存地址的寄存器) - `<offset>`:**偏移量**(立即数或寄存器,支持多种形式) --- #### 3. **寻址模式与偏移量类型** 根据引用[^2][^3],LDRB支持三类寻址方式: ##### (1) **立即数偏移** - **语法**:`LDRB Rd, [Rn, #imm]` - **地址计算**:$ \text{Addr} = \text{Rn} + \text{imm} $ - **偏移范围**: - 立即数$\text{imm}$为**12位无符号整数**(0~4095) - 示例: ```assembly LDRB R1, [R2, #8] ; 从地址(R2+8)加载字节到R1 ``` ##### (2) **寄存器偏移** - **语法**:`LDRB Rd, [Rn, Rm]` - **地址计算**:$ \text{Addr} = \text{Rn} + \text{Rm} $ - **特点**:偏移量来自寄存器,无范围限制 - 示例: ```assembly LDRB R3, [R4, R5] ; 地址 = R4 + R5 ``` ##### (3) **索引模式** - **前索引**(先更新基址): ```assembly LDRB Rd, [Rn, #imm]! ; 更新Rn = Rn + imm ``` - **后索引**(后更新基址): ```assembly LDRB Rd, [Rn], #imm ; 加载后更新Rn = Rn + imm ``` --- #### 4. **与相关指令对比** | **指令** | **数据宽度** | **扩展方式** | **特点** | |----------|--------------|------------------|------------------------------| | **LDRB** | 8位(字节) | 零扩展(高位补0)| 适合无符号数据(如ASCII字符)| | **LDR** | 32位(字) | 无扩展 | 需地址对齐,可能污染高位[^1] | | **LDRSB**| 8位(字节) | 符号扩展(高位补符号位) | 适合有符号数据 | > 示例:内存值 `0xAB` → LDRB加载为 `0x000000AB`,LDRSB加载为 `0xFFFFFFAB`[^2] --- #### 5. **典型应用场景** 1. **字符串处理**: ```assembly ; 遍历字符串(后索引模式) loop: LDRB R0, [R1], #1 ; 加载字符并自增指针 CMP R0, #0 ; 检测结束符 BNE loop ``` 2. **非对齐数据访问**: ```assembly LDRB R2, [R3, #3] ; 读取地址R3+3处的字节 ``` 3. **寄存器高位清零**: ```assembly LDRB R4, [R5] ; 确保R4高24位为0 ``` --- #### 6. **硬件行为与注意事项** - **零扩展实现**: 硬件直接复制内存字节到目标寄存器最低8位,高位填充0[^2]。 - **性能影响**: 频繁字节访问可能降低效率(对比批量加载指令LDM)[^3]。 - **地址错误**: 无效地址触发存储器异常(如总线错误)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值