【问题标题】:move data from double word array to byte array将数据从双字数组移动到字节数组
【发布时间】:2017-02-20 18:46:37
【问题描述】:

您好,我正在尝试将给定的双字数组划分为必须为字节的数组,

a dd 12345678h,1A2B3Ch,78h, ;given array

我只想添加不等于 0 的数字 如您所见,第一个数字没问题,第二个数字末尾有两个零 001A2B3Ch,第三个有六个零 00000078h

我编写了一个代码来执行此操作,对于第一个数字,它可以添加到 ASCII 代码为 78、56、34、12、28、2B 的数组字符中,并且它必须看起来像最后两个数字不正确(78,56,34,12,3C,2B,1A,78) 不知道为什么?

assume cs:code, ds:data
data segment
a dd 12345678h,1A2B3Ch,78h ;given array
l equ $-a
l1 equ l/4
zece db 10
pat dw 4
n db l dup(?) ;distination array
data ends

code segment
start:
    mov ax,data
    mov ds,ax

    mov cl,l1
    mov ch,0
    mov si,0
    mov ax,0
    mov bx,0

    repeta:
        mov bx,si
        mul pat
        mov al,byte ptr a[si]
        mov n[bx],al
        mov al,byte ptr a[si]+1
        add bx,1
        mov n[bx],al
        mov al,byte ptr a[si]+2
        add bx,1
        mov n[bx],al
        mov al,byte ptr a[si]+3
        add bx,1
        mov n[bx],al
        inc si
    loop repeta

mov ax,4C00h
int 21h
code ends
end start

【问题讨论】:

  • 所以从1A2B3Ch 你只想复制 3 个字节到目标数组,对吧?
  • 是的,它将是 3C、2B、1A

标签: assembly x86-16


【解决方案1】:

首先,始终了解您的数据,x86 内存可以按字节寻址。无论您使用哪种逻辑结构将数据写入内存,如果其他人正在查看内存内容,并且他们不知道您的逻辑结构,他们只会看到字节。

a dd 12345678h,1A2B3Ch,78h

所以这编译为 12 (3 * 4) 个字节:

78 67 34 12 3C 2B 1A 00 78 00 00 00

要通过删除零来压缩这样的数组,你甚至不需要使用双字,只需逐字节复制它(自愿放弃你最初认为它是双字数组的知识),跳过零值。

code segment
start:
    mov ax,data
    mov ds,ax

    lea     si,[a]      ; original array offset
    lea     di,[n]      ; destination array offset
    mov     cx,l        ; byte (!) length of original array

    repeta:
        ; load single byte from original array
        mov     al,[si]
        inc     si
        ; skip zeroes
        test    al,al
        jz      skipping_zero
        ; store non-zero to destination
        mov     [di],al
        inc     di
    skipping_zero:
        loop    repeta

    ; fill remaining bytes of destination with zeroes - init
    xor     al,al
    lea     si,[n+l]    ; end() offset of "n"
    ; jump first to test, so filling is skipped when no zero
    jmp     fill_remaining_test

    fill_remaining_loop:
        ; clear one more byte in destination
        mov     [di],al
        inc     di
    fill_remaining_test:
        ; test if some more bytes are to be cleared
        cmp     di,si       ; current offset < end() offset
        jb      fill_remaining_loop

    ; exit back to DOS
    mov ax,4C00h
    int 21h

code ends
end start

但不幸的是,这是对您的代码的完全重写,所以我会尝试添加一些解释,说明您的代码有什么问题。

关于MUL,尤其是关于乘以两个值的幂:

    mov     bx,si   ; bx = si (index into array?)
    mul     pat     ; dx:ax = ax * word(4)

如您所见,mul 既不使用bx,也不使用si,其结果为32 位值,分为dx(高位字)和ax(低位字) )。

要将si 乘以4,您必须这样做:

    mov     ax,si   ; ax = si
    mul     [pat]   ; dx:ax = ax * word(4)

或者简单地利用计算机正在处理位和整数值的二进制编码,因此要乘以 4,您只需将值中的位值“向上”(左)移动两个位置。

    shl     si,2    ; si *= 4 (truncated to 16 bit value)

但这会破坏原始的si(“索引”),因此人们通常不会这样做,而是调整循环增量。您将从si = 0 开始,但您将使用add si,4 而不是inc si。不再需要乘法。


add bx,1 伤害了我的眼睛,我更喜欢在人类组装中使用 inc bx(尽管在某些代的 x86 CPU 上,add bx,1 更快,但在现代 x86 上,inc 又可以了)。

mov al,byte ptr a[si]+1 是非常奇怪的语法,我更喜欢保持“类似英特尔”的简单,即。 mov al,byte ptr [si + a + 1]。它不是 C 数组,它实际上是从括号内的地址从内存中加载值。随着时间的推移,模​​仿 C 数组语法可能只会让您感到困惑。此外,byte ptr 可以从中删除,因为al 已经定义了数据宽度(除非您使用一些 MASM 对dd 数组强制执行此操作,但我不想用十英尺触摸微软的东西极)。

mov n[bx],al = mov [n + bx],almov [bx + n],al 也是如此,以代码中更有意义的为准。

但总的来说,在循环内部使用索引有点不寻常,通常您希望在 init 部分将所有索引转换为循环之前的地址,并在循环内部使用最终指针而不进行任何计算(按元素大小递增它们,即。 add si,4 用于双字)。那么你就不需要做任何索引乘法了。

特别是在 16 位模式下,寻址模式非常有限,在 32/64b 模式下,您至少可以将一个寄存器与常用大小(1、2、4、8)相乘,即。 mov [n + ebx * 4],eax = 不需要单独相乘。

编辑:在 16b 模式下没有可用的比例(乘以“索引”部分的 1/2/4/8),可能的示例 [si*4] 不起作用。


从最重要的 dword 字节存储字节的新变体(即反转 x86 dword 的 little-endian 方案):

code segment
start:
    mov     ax,data
    mov     ds,ax
    lea     si,[a]      ; original array offset
    lea     di,[n]      ; destination array offset
    mov     cx,l1       ; element-length of original array

    repeta:
        ; load four bytes in MSB-first order from original array
        ; and store only non-zero bytes to destination
        mov     al,[si+3]
        call    storeNonZeroAL
        mov     al,[si+2]
        call    storeNonZeroAL
        mov     al,[si+1]
        call    storeNonZeroAL
        mov     al,[si]
        call    storeNonZeroAL
        ; advance source pointer to next dword in array
        add     si,4
        loop    repeta

    ; Different ending variant, does NOT zero remaining bytes
    ; but calculates how many bytes in "n" are set => into CX:
    lea       cx,[n]      ; destination begin() offset
    sub       cx,di
    neg       cx          ; cx = number of written bytes into "n"

    ; exit back to DOS
    mov ax,4C00h
    int 21h

; helper function to store non-zero AL into [di] array
storeNonZeroAL:
    test    al,al
    jz      ignoreZeroAL
    mov     [di],al
    inc     di
ignoreZeroAL:
    ret

code ends
end start

以保持简短和简单的方式编写,而不是为了性能(我强烈建议您以相同的目标为目标,直到您对该语言感到非常熟悉为止,即使以简单的方式编写,对初学者来说也足够困难任何专家技巧)。

顺便说一句,您应该找到一些适合您的调试器,因此您可以逐条指令并观察“n”中的结果值是如何被添加的,以及为什么。或者您可能会很快注意到 bx+simul 没有按照您的预期进行,并且剩余的代码在错误的索引上运行。在没有调试器的情况下用汇编编程就像试图组装一个蒙着眼睛的机器人。

【讨论】:

  • 非常感谢您的所有建议,我是这种编程语言的新手,我们只使用 16b,请问您如何反转每个数字的结果(12, 34,56,78,1A,2B,3C,78)
  • 我不确定我是否理解你。你的意思是从最高有效字节开始的顺序?然后你将不得不使用 4 个字节的结构,不仅要复制非零值,还要颠倒每 4 个字节的顺序。与 Assembly 中的所有内容一样,它有许多可能的编码方式。
  • @Esan: ad "many possible ways": 如果您了解您的数据,以及您想要实现的计算,您只需将计算分解为更简单的步骤,直到步骤如此简单,它们类似于 CPU 指令。然后你编写代码来做这件事。汇编不是要学习哪条指令会“反转字符串”/等等……您只需了解该指令对寄存器和内存的作用。然后你必须了解如何存储各种数据。并找出计算您想要的结果的公式。然后你只需在 CPU 指令中编写该计算 = 完成。
  • @Esan:另外,如果您正在学习汇编,我最近确实以健康的好奇心回答了某人,我认为解决方案很简单,您可以理解它(使用调试器和指令参考指南来验证它们是如何工作的),但您可能会很有趣地看到问题是如何构建的(OP 并没有停止寻找第一个解决方案,而是对其进行更多质疑和推理),以及如何找到答案(数学理论和计算再次......“计算机”包含“计算”一词并非偶然):stackoverflow.com/q/42245263/4271923
  • "其实我觉得[si*4]是16b模式下少数几个合法的寻址方式之一?"很抱歉,8086 只允许基址寄存器BX/BP 和索引寄存器SI/DI 的组合。不存在任何缩放形式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
相关资源
最近更新 更多