如果要将值视为已签名,则需要movsx
. 假设 NASM 语法:
default rel
; ... declarations and whatever
movsx rax, word [a + 1*2] ; a is an array of dw = words
movsx rcx, byte [b + 1*1] ; b is an array of db = bytes
add rax, rcx
mov [result], rax ; result is a qword
(MASM 或 GNU .intel_syntax 将使用word ptr
而不是word
,只需添加ptr
到内存操作数的大小说明符。)
1
可以是一个寄存器,这样您[a + rsi*2]
就[b + rsi]
可以轻松地遍历您的数组。 引用内存位置的内容。(x86 寻址模式)
我写1*2
的不是 2,而是表示它是索引 1(第二个数组元素),按元素大小缩放。汇编器将对常量表达式求值,并仅使用与它相同的(RIP 相对)寻址模式,[a]
但具有不同的偏移量。
如果您需要它在与位置无关的代码中工作(在这种情况下,您不能[disp32 + register]
对符号使用具有 32 位绝对地址的寻址模式),lea rdi, [a]
请首先使用 (RIP-relative LEA) 并执行[rsi + rsi*2]
.
如果你想要零扩展,你会使用movzx
movzx eax, word [a + 1*2] ; a is an array of dw = words
movzx ecx, byte [b + 1*1] ; b is an array of db = bytes
; word and byte zero-extended into 64-bit registers:
; explicitly to 32-bit by MOVZX, and implicitly to 64-bit by writing a 32-bit reg
; add eax, ecx ; can't overflow 32 bits, still zero-extended to 64
sub rax, rcx ; want the full width 64-bit signed result
mov [result], rax ; result is a qword
如果您知道完整结果的高位始终为零,则只需使用 EAX(32 位操作数大小),除了末尾。 在 x86-64 中使用 32 位寄存器/指令的优点
这段代码对应C之类的
static uint16_t a[] = {...};
static uint8_t b[] = {...};
static int64_t result;
void foo(){
int64_t rax = a[1] - (int64_t)b[1];
result = rax; // why not just return this like a normal person instead of storing?
}
说到这,您可以查看Godbolt 编译器资源管理器上的编译器输出并查看这些指令和寻址模式。
请注意,这mov al, [b + 1]
将加载一个字节并将其合并到 RAX 的低字节中。
你通常不想要这个;movzx
是在现代 x86中加载字节的正常方式。现代 x86 CPU 将 x86 解码为类似 RISC 的内部微指令,用于寄存器重命名 + 乱序执行。 movzx
避免对完整寄存器的旧值的任何错误依赖。它类似于 ARM ldrb
、 MIPSlbu
等。
合并到 RAX 的低字节或字是 x86 可以做的奇怪的 CISC 事情,但 RISC 不能。
您可以安全地读取8 位和 16 位寄存器(并且您需要进行字存储),但通常避免写入部分寄存器,除非您有充分的理由,并且您了解可能的性能影响(为什么 GCC 不使用部分注册?)。例如,您已经在 cmp + 之前对整个目的地进行了异或归零setcc al
。