【问题标题】:Iterating through arrays in Assembly to solve general equation遍历Assembly中的数组以求解一般方程
【发布时间】:2020-10-31 15:01:36
【问题描述】:

我正在使用 NASM 学习汇编语言,并且遇到了一个我似乎无法弄清楚的编程问题。该程序的目标是求解这个方程: Picture of Equation 对于那些看不到照片的人,等式表示对于两个长度为 n 的数组,数组 a 和数组 b,求:对于 i=0 到 n-1,((ai + 3) - (bi - 4))

我应该只使用三个通用寄存器,并且我已经找到了一个我认为可能有效的代码示例,但是我在第 16 行和第 19 行中一直遇到逗号和操作数错误。我理解这是为了遍历数组,您需要将指针移动到每个索引,但由于两个数组的值不同(数组 1 是 dw,数组 2 是 db)我不确定如何解释这一点。我对大会还是很陌生,任何帮助或指示将不胜感激。 这是我当前代码的图片: Code Sample

       segment .data
  a         dw    12, 14, 16      ; array of three values        
  b         db    2, 4, 5         ; array of three values
  n         dw    3               ; length of both arrays
  result    dq    0               ; memory to result
      segment .text
      global main
  main:
      mov   rax, 0
      mov   rbx, 0
      mov   rdx, 0
  loop_start:
      cmp   rax, [n]
      jge   loop_end
      
      add   rbx, a[rax*4]   ; adding element of a at current index to rbx
      add   rbx, 3          ; adding 3 to current index value of array a in rbx
      
      add   rdx, BYTE b[rax]
      sub   rdx, 4

      sub   rbx, [rdx]
      add   [result], rbx
      
      xor   rbx, rbx
      xor   rdx, rdx
      add   rax, 1
  loop_end:
      ret 

【问题讨论】:

    标签: arrays assembly x86-64 nasm


    【解决方案1】:

    您使用的是 16 位和 8 位数据,但使用的是 64 位寄存器。一般来说,处理器需要相同的数据大小,尽管任何一条指令的操作数都是一样的。

    cmp rax,[n] 具有不同的数据大小,这是不允许的:rax 是 64 位寄存器,[n] 是 16 位数据项。所以,我们可以把它改成cmp ax,[n],现在一切都是16位的了。

    add rbx,a[rax*4] 也在混合不同大小的操作数(不允许)。 rbx 是 64 位,a[] 是 16 位。您可以将寄存器更改为bx,这将被允许。但也要注意*4 太多了,应该是*2,因为dw 是16 位数据(2 字节),而不是32 位(4 字节)。由于您正在清除rbx,因此您不需要add,您可以简单地使用mov

    add rdx, BYTE b[rax] 也在混合不同的大小。 rax 是 64 位宽,而 b[] 是 8 位宽。使用dl 而不是rdx。没有什么可添加的,因此您应该使用mov 而不是add。现在dl 中有一个值,并且您之前清除了rdx,您可以切换到使用dx(来自dl)这将具有b[i] 的16 位值。

    sub rbx, [rdx] 有错误的尊重。在这里你只想sub bx,dx

    您没有使用标签loop_start,因此没有循环。 (在循环末尾添加一个向后分支。)

    【讨论】:

    • 在 NASM 中,数据声明不将大小与标签相关联。完全允许cmp rax,[n] - 64 位寄存器意味着另一个操作数的操作数大小。这当然不是您想要 做的(因为您加载的高 6 字节来自 result: dq 0),但这只是您的程序中的一个错误,而不是机器无法做到的。或者换一种说法,NASM 不能帮助您检测这样的错误。它只是组装你写的东西。 Asm 没有变量,只有原始字节,x86 允许未对齐的加载。
    【解决方案2】:

    ...但是由于两个数组的值不同(数组 1 是 dw,数组 2 是 db)我不确定如何解释这一点

    Erik Eidt's answer 解释了为什么您“不断遇到逗号和操作数错误”。虽然您可以恢复使用较小的寄存器(添加操作数大小前缀),但我的回答采用了不同的方法。

    该指令集具有movzx(使用零扩展移动)和movsx(使用符号扩展移动)指令来处理这些不同的大小。看看下面如何使用这些。

    我也应用了一些更改。

    • 不要错过简化计算的机会:

      ((a[i] + 3) - (b[i] - 4)) 等价于(a[i] - b[i] + 7)

    • 这些数组都不是空的,所以你可以把循环条件放在它的下面。

    • 如果方便,您可以从末尾开始处理数组。求和运算无所谓!

         segment .data
    a      dw    12, 14, 16      ; array of three values        
    b      db    2, 4, 5         ; array of three values
    n      dw    3               ; length of both arrays
    result dq    0               ; memory to result
        segment .text
        global main
    main:
           movzx rcx, word [n]
    loop_start:
           movzx rax, word [a + rcx * 2 - 2]
           movzx rbx, byte [b + rcx - 1]
           lea   rax, [rax + rbx + 7]
           add   [result], rax
           dec   rcx
           jnz   loop_start
           ret
    

    请注意,额外的负偏移量 - 2- 1 的存在是为了补偿循环控制在 {2, 1, 0} 本来是完美的情况下采用值 {3, 2, 1} 的事实.这不会在指令中引入额外的位移分量,因为提到 ab 数组实际上已经是位移。

    虽然这被标记为 x86-64,但您可以使用 32 位寄存器编写整个内容,而不需要 REX 前缀。相同的结果

         segment .data
    a      dw    12, 14, 16      ; array of three values        
    b      db    2, 4, 5         ; array of three values
    n      dw    3               ; length of both arrays
    result dq    0               ; memory to result
        segment .text
        global main
    main:
           movzx ecx, word [n]
    loop_start:
           movzx eax, word [a + ecx * 2 - 2]
           movzx ebx, byte [b + ecx - 1]
           lea   eax, [eax + ebx + 7]
           add   [result], eax
           dec   ecx
           jnz   loop_start
           ret
    

    【讨论】:

    • [a + ecx * 2 - 2] 需要地址大小前缀才能在 x86-64 寻址模式下使用 32 位寄存器。 movzx eax, word [a + rcx * 2 - 2] 将是编写该指令的最小和最有效的方式。我们知道 ECX 已经正确地零扩展为 RCX。当然,在内存中拥有n: dw 3 是很愚蠢的。在bmov ecx, n 之后应该是n equ $ - b
    • 另外,使用default rel 这样[result] 之类的寻址模式可以是RIP-relative,而不是浪费一个SIB 字节来编码一个32 位绝对(符号扩展)地址。此外,在循环之后一次累积到寄存器存储会更有效,即使它需要额外的指令。将循环变量保存在内存中对性能非常不利(例如调试模式)。最初的问题是这样做的,但是如果您要指出效率的提高,那对于速度来说是最重要的(尽管不是大小)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 2019-12-11
    • 2012-11-20
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    相关资源
    最近更新 更多