用于寻址的 MASM 偏移量与标签
MASM Offset vs. Label for addressing
我目前正在阅读 Irvine x86 Assembly 一书,我正在学习第四章。
他们引入了 OFFSET
指令,但我对为什么要使用它感到困惑。为什么我不直接使用标签(它已经是该数据的地址)?似乎 OFFSET
只是增加了额外的噪音。
我有这个小程序来说明我的观点。我有一个名为 array
的数据标签,我可以将数组的元素移动到 al
中。但是书上说的是使用OFFSET
指令获取array
的地址,并将其移动到esi
。但这对我来说似乎没有必要,因为我可以使用标签。
我有两段代码在下面做同样的事情。一个是我使用标签访问数组的元素,另一个是我使用 OFFSET
将地址移动到 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
的地址。但后来他们说,"Optionally, you can delcare ptrB with the OFFSET
operator to make the relationship clearer:"
ptrB dword OFFSET arrayB
这并没有让我更清楚。我已经知道 arrayB
是一个地址。看起来 OFFSET
只是被扔在那里,并没有真正做任何事情。从最后一行中删除 OFFSET
确实可以达到同样的效果。如果我仍然可以使用标签来获取地址,OFFSET
到底有什么用?
Are they really just two ways to achieve the same thing?
是的,程序集有许多 种方法。
C 等价物是
char *p = array;
然后使用 p[0]
、p[1]
等与使用 array[0]
、array[1]
等
将指针放在寄存器中的好处是在重复使用时可以节省一些代码量; 2 字节 mov
指令,仅包含操作码 + ModRM,而不是针对 [disp32]
寻址模式将绝对地址分别编码到每条指令中。
另一个优点是你可以增加指针inc esi
。在您没有完全展开循环的其他情况下,您需要寄存器中的指针或索引。
普通指针通常优于 [array + ecx]
,尤其优于 [array + ecx*4]
,因为索引寻址模式有一些缺点。 ([array + ecx]
在技术上没有索引;它是 [base + disp32]
并且不需要 SIB 字节,并且不算作 Micro fusion and addressing modes 的索引)。
不过,您可以使用字节偏移量(例如 add ecx, TYPE array
),以允许 [base + disp32]
寻址模式进入 int
的静态数组而不是 [disp32 + idx*scale]
。
每次都使用[disp32]
可以避免需要额外的指令将地址放入寄存器。 mov reg, imm32
只是一个 5 字节的单 uop 指令,但在几个静态数组访问之前,它可能仍然不值得为了性能而这样做。这可能取决于您的代码在 uop 缓存中已经很热的频率与它必须 fetch/decode 的频率。 (节省代码大小提高了 L1 I$ 命中率,或者至少意味着更多指令适合一个缓存行,因此如果它在不在最热内循环中的东西中节省代码大小,那么使用更多指令/更多 uops 是值得的.)
在循环之前(未完全展开),您通常需要一条指令将循环计数器/索引归零,例如 xor ecx, ecx
。使用 mov reg, imm32
仅长 3 个字节,并且没有额外的微指令。如果您每次使用指针而不是索引寻址模式时都节省 4 或 5 个字节,那么您已经从每次迭代中仅使用一个数组引用中脱颖而出。并且没有额外的微指令。 (忽略执行异或归零指令与 mov-immediate 指令的循环外成本之间的任何细微差异。)
请注意,对于 x86-64,您通常会将静态地址放入具有 7 字节 RIP 相对 LEA 的寄存器中。对于你的代码根本就是 LargeAddressAware,你不能使用 [array + rcx]
因为它只适用于 [disp32 + reg]
寻址模式,而不是 [RIP + rel32]
.
顺便说一句,为了保持一致性,我推荐这个超过 mov al, array
mov al, [array + 0]
mov al, [array + 1]
...
您问题下的第一条评论来自您对 mov al, array
然后 mov al, [array + 1]
对相似地址使用 2 种不同语法感到困惑的人;我想 Jester 认为你 打算 像 mov al, OFFSET array
这样的事情。顺便说一句,你可以这样写(我认为)
mov al, array
mov al, array + 1
但为了清楚起见,我总是建议在内存操作数周围使用方括号。 尤其是当您查看 NASM 语法时, 总是 是必需的,但即使您只使用 MASM,也有人建议采用该约定。 (但请注意,在某些情况下,当没有寄存器时,MASM 会 忽略 括号:Confusing brackets in MASM32 所以不要认为在 MASM 中使用括号会使它像 NASM.)
顺便说一句,加载单个字节的性能高效方法是将其零扩展到完整寄存器中,而不是合并到完整寄存器的低字节中。 movzx eax, byte ptr [esi]
顺便说一句,是的,mov esi, OFFSET array
(5 字节)是将静态地址放入寄存器(代码大小和性能)的最有效方法。 lea esi, array
是6个字节(操作码+modrm+[disp32]
寻址方式),可以运行在更少的执行端口上;切勿在 32 位模式下使用没有寄存器的 LEA。
在 64 位模式下,您需要 lea rsi, array
,因为 MASM 会自动使用 RIP 相对寻址,这正是您想要的。否则,对于非 LargeAddressAware 的代码仍然使用 mov esi, OFFSET array
(是 ESI,不是 RSI)并且仍然可以利用使用 32 位绝对地址的紧凑代码。
我目前正在阅读 Irvine x86 Assembly 一书,我正在学习第四章。
他们引入了 OFFSET
指令,但我对为什么要使用它感到困惑。为什么我不直接使用标签(它已经是该数据的地址)?似乎 OFFSET
只是增加了额外的噪音。
我有这个小程序来说明我的观点。我有一个名为 array
的数据标签,我可以将数组的元素移动到 al
中。但是书上说的是使用OFFSET
指令获取array
的地址,并将其移动到esi
。但这对我来说似乎没有必要,因为我可以使用标签。
我有两段代码在下面做同样的事情。一个是我使用标签访问数组的元素,另一个是我使用 OFFSET
将地址移动到 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
的地址。但后来他们说,"Optionally, you can delcare ptrB with the OFFSET
operator to make the relationship clearer:"
ptrB dword OFFSET arrayB
这并没有让我更清楚。我已经知道 arrayB
是一个地址。看起来 OFFSET
只是被扔在那里,并没有真正做任何事情。从最后一行中删除 OFFSET
确实可以达到同样的效果。如果我仍然可以使用标签来获取地址,OFFSET
到底有什么用?
Are they really just two ways to achieve the same thing?
是的,程序集有许多 种方法。
C 等价物是
char *p = array;
然后使用 p[0]
、p[1]
等与使用 array[0]
、array[1]
等
将指针放在寄存器中的好处是在重复使用时可以节省一些代码量; 2 字节 mov
指令,仅包含操作码 + ModRM,而不是针对 [disp32]
寻址模式将绝对地址分别编码到每条指令中。
另一个优点是你可以增加指针inc esi
。在您没有完全展开循环的其他情况下,您需要寄存器中的指针或索引。
普通指针通常优于 [array + ecx]
,尤其优于 [array + ecx*4]
,因为索引寻址模式有一些缺点。 ([array + ecx]
在技术上没有索引;它是 [base + disp32]
并且不需要 SIB 字节,并且不算作 Micro fusion and addressing modes 的索引)。
不过,您可以使用字节偏移量(例如 add ecx, TYPE array
),以允许 [base + disp32]
寻址模式进入 int
的静态数组而不是 [disp32 + idx*scale]
。
每次都使用[disp32]
可以避免需要额外的指令将地址放入寄存器。 mov reg, imm32
只是一个 5 字节的单 uop 指令,但在几个静态数组访问之前,它可能仍然不值得为了性能而这样做。这可能取决于您的代码在 uop 缓存中已经很热的频率与它必须 fetch/decode 的频率。 (节省代码大小提高了 L1 I$ 命中率,或者至少意味着更多指令适合一个缓存行,因此如果它在不在最热内循环中的东西中节省代码大小,那么使用更多指令/更多 uops 是值得的.)
在循环之前(未完全展开),您通常需要一条指令将循环计数器/索引归零,例如 xor ecx, ecx
。使用 mov reg, imm32
仅长 3 个字节,并且没有额外的微指令。如果您每次使用指针而不是索引寻址模式时都节省 4 或 5 个字节,那么您已经从每次迭代中仅使用一个数组引用中脱颖而出。并且没有额外的微指令。 (忽略执行异或归零指令与 mov-immediate 指令的循环外成本之间的任何细微差异。)
请注意,对于 x86-64,您通常会将静态地址放入具有 7 字节 RIP 相对 LEA 的寄存器中。对于你的代码根本就是 LargeAddressAware,你不能使用 [array + rcx]
因为它只适用于 [disp32 + reg]
寻址模式,而不是 [RIP + rel32]
.
顺便说一句,为了保持一致性,我推荐这个超过 mov al, array
mov al, [array + 0]
mov al, [array + 1]
...
您问题下的第一条评论来自您对 mov al, array
然后 mov al, [array + 1]
对相似地址使用 2 种不同语法感到困惑的人;我想 Jester 认为你 打算 像 mov al, OFFSET array
这样的事情。顺便说一句,你可以这样写(我认为)
mov al, array
mov al, array + 1
但为了清楚起见,我总是建议在内存操作数周围使用方括号。 尤其是当您查看 NASM 语法时, 总是 是必需的,但即使您只使用 MASM,也有人建议采用该约定。 (但请注意,在某些情况下,当没有寄存器时,MASM 会 忽略 括号:Confusing brackets in MASM32 所以不要认为在 MASM 中使用括号会使它像 NASM.)
顺便说一句,加载单个字节的性能高效方法是将其零扩展到完整寄存器中,而不是合并到完整寄存器的低字节中。 movzx eax, byte ptr [esi]
顺便说一句,是的,mov esi, OFFSET array
(5 字节)是将静态地址放入寄存器(代码大小和性能)的最有效方法。 lea esi, array
是6个字节(操作码+modrm+[disp32]
寻址方式),可以运行在更少的执行端口上;切勿在 32 位模式下使用没有寄存器的 LEA。
在 64 位模式下,您需要 lea rsi, array
,因为 MASM 会自动使用 RIP 相对寻址,这正是您想要的。否则,对于非 LargeAddressAware 的代码仍然使用 mov esi, OFFSET array
(是 ESI,不是 RSI)并且仍然可以利用使用 32 位绝对地址的紧凑代码。