Assembly MASM偏移量与寻址标签

Assembly MASM偏移量与寻址标签,assembly,masm,irvine32,Assembly,Masm,Irvine32,我目前正在阅读Irvine x86汇编手册,我正在阅读第四章 他们已经引入了OFFSET指令,但我不明白为什么要使用它。为什么我不直接使用已经是数据地址的标签呢?看起来偏移只是增加了额外的噪声 我有这个小程序来说明我的观点。我有一个称为array的数据标签,我可以将数组中的元素移动到al中。但这本书讨论的是使用OFFSET指令获取数组的地址并将其移动到esi。但这对我来说似乎没有必要,因为我可以使用标签 我有两段代码在下面做同样的事情。一个是使用标签访问数组元素,另一个是使用偏移量将地址移动到e

我目前正在阅读Irvine x86汇编手册,我正在阅读第四章

他们已经引入了OFFSET指令,但我不明白为什么要使用它。为什么我不直接使用已经是数据地址的标签呢?看起来偏移只是增加了额外的噪声

我有这个小程序来说明我的观点。我有一个称为array的数据标签,我可以将数组中的元素移动到al中。但这本书讨论的是使用OFFSET指令获取数组的地址并将其移动到esi。但这对我来说似乎没有必要,因为我可以使用标签

我有两段代码在下面做同样的事情。一个是使用标签访问数组元素,另一个是使用偏移量将地址移动到esi中,然后访问数组元素

.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode: DWORD

.data
    array   BYTE 10h, 20h, 30h, 40h, 50h

.code
main PROC
    xor eax, eax        ; set eax to 0

    ; Using Labels
    mov al, array      
    mov al, [array + 1]
    mov al, [array + 2]
    mov al, [array + 3]
    mov al, [array + 4]

    ; Using Offset
    mov esi, OFFSET array
    mov al, [esi]
    mov al, [esi + 1]
    mov al, [esi + 2]
    mov al, [esi + 3]
    mov al, [esi + 4]

    INVOKE ExitProcess, 0
main ENDP
END main
它们真的只是实现同一目标的两种方法吗

在本书后面讨论指针时,他们有以下示例:

.data
arrayB byte 10h, 20h, 30h, 40h
ptrB dword arrayB
这对我来说很有意义。ptrB持有arrayB的地址。但是他们说,您可以选择使用OFFSET操作符delcare ptrB,以使关系更清晰:

ptrB dword OFFSET arrayB
这对我来说一点也不清楚。我已经知道arrayB是一个地址。看起来偏移量只是被扔进去了,它实际上什么都没做。从最后一行中删除偏移量将实现同样的效果。如果我可以用一个标签来获取地址,那么OFFSET到底能做什么呢

它们真的只是实现同一目标的两种方法吗

是的,组装有很多方法

C的等价物是 char*p=数组;然后使用p[0]、p[1]等,而不是使用数组[0]、数组[1]等

将指针放入寄存器的优点是,当您重复使用它时,它可以节省一些代码大小;2字节mov指令,仅使用opcode+ModRM,而不是在[disp32]寻址模式下将绝对地址单独编码到每条指令中

另一个优点是可以使用inc-esi增加指针。在其他不完全展开循环的情况下,需要在寄存器中使用指针或索引

普通指针通常优于[array+ecx],尤其优于[array+ecx*4],因为索引寻址模式有一些缺点。[array+ecx]在技术上没有索引;它是[base+disp32],不需要SIB字节,也不算作索引

您可以使用字节偏移量,例如添加ecx,键入array,以允许[base+disp32]寻址模式进入int的静态数组,而不是[disp32+idx*scale]

每次使用[disp32]可避免需要额外的指令将地址放入寄存器。mov reg、imm32只是一条5字节的单uop指令,但在几个静态数组访问之前,它的性能可能仍然不值得。这可能取决于您的代码在uop缓存中已处于热状态的频率与必须获取/解码的频率。保存代码大小可以提高L1 I$命中率,或者至少意味着在一个缓存线中可以容纳更多的指令,因此,如果它将代码大小保存在不在最热的内部循环中的内容中,则使用更多指令/更多UOP是值得的

在循环未完全展开之前,通常需要一条指令将循环计数器/索引归零,如xor ecx、ecx。使用mov reg,imm32只长3个字节,并且没有额外的UOP。如果每次使用指针而不是索引寻址模式时都要节省4或5个字节,那么每次迭代只需要一个数组引用,就已经领先了。并且不需要额外的UOP。忽略执行xor归零与mov立即指令的外部循环成本之间的任何微小差异

请注意,对于x86-64,您通常会将一个静态地址放在一个寄存器中,该寄存器具有一个7字节的RIP相对LEA。如果你的代码是大地址软件,你不能使用[array+rcx],因为它只适用于[disp32+reg]寻址模式,而不是[RIP+rel32]

顺便说一句,为了保持一致性,我建议将其与mov al、阵列进行比较

在你的问题下的第一个评论来自于某人,你在做mov-al,array,然后mov-al,[array+1]时对相似地址使用了两种不同的语法,这让你感到困惑;我想Jester以为你想要的是mov al,偏移阵列。顺便说一句,我想你可以这样写

mov al, array
mov al, array + 1
但为了清晰起见,我总是建议在内存操作数周围使用方括号。特别是当您查看NASM语法时,总是需要这样做,但有些人建议使用这种约定,即使您只使用MASM。但请注意,在某些情况下,当没有寄存器时,MASM确实会忽略括号:所以不要认为在MASM中使用括号会使它像NASM一样工作

顺便说一句,加载单个字节的有效方法是将其零扩展到完整寄存器中,而不是合并到完整寄存器的低字节中 登记movzx eax,字节ptr[esi]

顺便说一句,是的,movesi,OFFSET数组5字节是将静态地址放入寄存器代码大小和性能的最有效方法。lea esi,阵列为6字节操作码+modrm+[disp32]寻址模式,可在较少的执行端口上运行;在32位模式下,切勿在没有寄存器的情况下使用LEA


在64位模式下,您需要lea rsi、array,因为MASM会自动使用RIP相对寻址,这是您想要的。否则仍然使用mov esi,OFFSET array yes esi,而不是RSI来处理不是大型Addressware的代码,并且仍然可以利用使用32位绝对地址的紧凑代码。

您的第一个版本不需要mov al,array,它是无用的。请注意,在masm中,数组不是地址,而是至少在指令中的值。例如,Irvine的WriteString需要寄存器中的地址,您可以使用偏移量获得地址。您也可以使用LEA,但这是一个品味的问题。@Jester:它并不比任何其他未被读取就被覆盖的加载更无用:P请注意相应的mov al,[esi]。为了与其他负载保持一致,OP可以将mov al、[array+0]写入,或者将其他负载写入mov al、array+1等,或者更好,写入movzx eax、字节ptr[esi+0]等@Roberttevanic:另一种方法是LEA:LEA esi,array。这将把数组的第一个字节的地址加载到ESI中。LEA有一些棘手且令人困惑的应用程序,所以作为初学者,请保持在OFFSET上。@rkhb:更重要的是,使用LEA将32位常量放入寄存器没有任何好处。与mov immediate相比,这是对代码大小的浪费,因此,在16位或32位代码的寻址模式中,如果没有寄存器,就不应该使用它。感谢您提供了非常全面的答案。我想确认我的两个例子有相同的结果,你和C的比较让我明白了做事情的方法不止一种。我只是想确保我没有遗漏任何关于偏移量的信息,我认为我没有遗漏,但你已经展示了为什么把地址放在寄存器中是有利的,为什么有人会使用它。我已经把你的答案读了好几遍了,这里有很多东西要我消化。再次感谢。
mov al, array
mov al, array + 1