【问题标题】:MOVing between two memory addresses在两个内存地址之间移动
【发布时间】:2010-11-20 21:48:21
【问题描述】:

我正在尝试学习汇编(请多多包涵),但在这一行出现编译错误:

mov byte [t_last], [t_cur]

错误是

error: invalid combination of opcode and operands

我怀疑这个错误的原因仅仅是 mov 指令不可能在两个内存地址之间移动,但是半小时的谷歌搜索我无法确认 - 是这样吗?

另外,假设我是对的,这意味着我需要使用寄存器作为复制内存的中间点:

mov cl, [t_cur]
mov [t_last], cl

推荐使用什么寄存器(或者我应该使用堆栈代替)?

【问题讨论】:

标签: assembly x86 instructions mov


【解决方案1】:

技术上可以从内存移动到内存。

尝试使用MOVS(移动字符串),根据是否设置[E]SI[E]DI 您要传输字节、字等。

    mov si, t_cur    ; Load SI with address of 't_cur'
    mov di, t_last   ; Load DI with address of 't_last'
    movsb            ; Move byte from [SI] to [DI]

    ; Some dummy data
    t_cur    db 0x9a ; DB tells NASM that we want to declare a byte
    t_last   db 0x7f ; (See above)

但是请注意,这比执行 MOV 两次效率低,但它确实在一条指令中执行了副本。

MOVS 的使用方法及其工作原理如下: https://www.felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq

MOVS 指令几乎从不单独使用,并且大部分与 REP 前缀一起使用。

现代 CPU 具有相当高效的 rep movs 实现,接近使用 AVX 矢量加载/存储指令的循环速度。

    ; - Assuming that 't_src' and 't_dst' are valid pointers
    mov esi, t_src  ; Load ESI with the address of 't_src'
    mov edi, t_dst  ; Load EDI with the address of 't_dst'
    mov ecx, 48     ; Load [ER]CX with the count (let's say 48 dwords =   blocks)
    rep movsd       ; Repeat copying until ECX == 0

从逻辑上讲,复制发生在 48 个 4 字节 dword 块的副本中,但真正现代的 CPU(快速字符串/ERMSB)将使用 16 或 32-字节块来提高效率。

本手册解释了应该如何使用REP,以及它是如何工作的: https://www.felixcloutier.com/x86/rep:repe:repz:repne:repnz

【讨论】:

  • +1 用于解释 movsb 是什么并链接手册,这与提出 movs 的其他现有答案不同。我对您对 not 的回答进行了一些编辑,实际上建议您这样做,因为与 mov-load + mov-store 相比,使用像 AL 这样的临时寄存器没有任何好处。 (除了单处理器系统上的中断可能是原子性,但这是一个非常具体的用例,一般来说没有什么帮助。)但无论如何,欢迎来到 Stack Overflow :)
  • 您还必须确保段寄存器 DS 和 ES 设置正确。如果编写可能使用 ES != DS 的多个段的 DOS 程序,情况可能并非如此。移动从 DS:E(SI) 复制到 ES:E(DI)。 OP 没有提到操作系统,所以可能无法保证 DS==ES
【解决方案2】:

没错,x86 机器码不能用两个显式内存操作数([] 中指定的任意地址)对指令进行编码

推荐的寄存器是什么

您不需要保存/恢复的任何寄存器。

在所有主流的 32 位和 64 位调用约定中,EAX、ECX 和 EDX 都是 call-clobbered,因此 AL、CL 和 DL 是不错的选择。对于字节或字副本,您通常希望将movzx 加载到 32 位寄存器中,然后是 8 位或 16 位存储。这避免了对寄存器旧值的错误依赖。如果您主动想要合并到另一个值的低位,请仅使用窄的 16 位或 8 位 mov 加载。 x86 的 movzx 类似于 ARM ldrb 等指令。

    movzx   ecx,  byte [rdi]       ; load CL, zero-extending into RCX
    mov    [rdi+10], cl

在 64 位模式下,SIL、DIL、r8b、r9b 等也是不错的选择,但需要在存储的机器代码中使用 REX 前缀,因此有一个较小的代码大小理由来避免它们。

出于性能原因,通常避免写入 AH、BH、CH 或 DH,除非您已阅读并理解以下链接,并且任何错误的依赖关系或部分寄存器合并停止不会成为问题或根本不会发生在你的代码中。


(或者我应该使用堆栈代替)?

首先,您根本无法推送单个字节,因此您无法从堆栈中进行字节加载/字节存储。对于 word、dword 或 qword(取决于 CPU 模式),您可以使用push [src] / pop [dst],但这比通过寄存器复制要慢得多。在可以从最终目的地读取数据之前,它会引入额外的存储/重新加载存储转发延迟,并且需要更多的微指令。

除非堆栈上的某处所需的目的地,并且您无法将该局部变量优化到寄存器中,在这种情况下push [src] 可以将其复制到那里并为它。

查看https://agner.org/optimize/the x86 tag wiki 中的其他x86 性能链接

【讨论】:

    【解决方案3】:

    只想和你讨论“记忆障碍”。 在c代码中

    a = b;//Take data from b and puts it in a
    

    将被组装到

    mov %eax, b # suppose %eax is used as the temp
    mov a, %eax
    

    系统无法保证赋值的原子性。这就是为什么我们需要人民币 (阅读障碍)

    【讨论】:

    • x86 不能以原子方式从内存复制到内存。障碍不会产生原子性,它们只会停止重新排序(编译时或运行时或两者,取决于障碍)。
    • @YuvalKeysar:您的编辑留下了一个未修复的错误(我之前没有注意到):在 AT&T 语法中,目的地排在第二位。这个 asm 实际上将 EAX 存储到 b,然后从 a 加载 EAX。这个答案只需要删除,IMO,因为关于障碍的讨论是无稽之谈。
    【解决方案4】:

    你的怀疑是正确的,你不能从一个记忆移到另一个记忆。

    任何通用寄存器都可以。如果您不确定其中的内容,请记住推送寄存器,并在完成后将其恢复。

    【讨论】:

    • 与将数据本身推入堆栈相比,使用寄存器有什么优势吗?
    • 推入并稍后从堆栈中弹出会增加两个额外的内存访问。
    【解决方案5】:

    在 16 位中真的很简单,只需执行以下操作:

         push     di
         push     si
         push     cx
         mov      cx,(number of bytes to move)
         lea      di,(destination address)
         lea      si,(source address)
         rep      movsb
         pop      cx
         pop      si
         pop      di
    

    注意:如果您需要保存寄存器的内容,则需要推送和弹出。

    【讨论】:

    • +1,因为在某些情况下,最好了解工具箱中的所有工具。 rep movsb/movsw 是 1 字节操作码,IIRC
    • 根据架构,您可以使用 pusha 而不是单独推送所有寄存器,使用 popa 而不是全部弹出。
    • 这也适用于 32 位和 64 位,但它使用该位系统的寄存器
    【解决方案6】:

    还有一个将数据从内存移动到内存的MOVS命令:

    MOV SI, OFFSET variable1
    MOV DI, OFFSET variable2
    MOVS
    

    【讨论】:

    • 会起作用,但需要格外小心:您需要保存 si 和 di 寄存器。我想复制一个字节不值得。
    • x86 上的字符串命令可以被认为是过时的。永远不要使用它们。它们永远不会比“手动”复制快,但在大多数情况下要慢得多。
    • @hirschhornsalz,很抱歉死灵,但你有关于字符串命令基本上过时的详细信息吗?
    • 借助 ERMS 和 FSRM 等新功能,使用 rep 的字符串操作再次变得快速。请参阅下面的clapdrop的答案
    猜你喜欢
    • 1970-01-01
    • 2019-04-05
    • 2022-01-14
    • 1970-01-01
    • 2017-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多