将数据从双字数组移动到字节数组

时间:2017-02-20 18:46:37

标签: assembly x86-16

您好我试图将给定的双字数组除以必须为字节的数组

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

1 个答案:

答案 0 :(得分:3)

首先,始终了解您的数据,x86内存可以按字节寻址。使用什么样的逻辑结构将数据写入内存并不重要,如果其他人正在观察内存内容,并且他们不了解您的逻辑结构,他们只看到字节。

a dd 12345678h,1A2B3Ch,78h

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

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

要通过删除零来压缩这样的数组,你甚至不需要使用双字,只需逐字节复制它(自愿丢弃你原来的双字数组的知识),跳过零值。 / p>

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不使用bxsi,而是导致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开始,但不是inc si,而是add si,4。不再需要乘法。

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]不起作用。

新变量存储来自最高有效双字节的字节(即,颠倒x86双字的小端方案):

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 + si vs mul没有按预期执行,其余代码在错误的索引上运行。在没有调试器的情况下进行汇编编程就像试图组装一个被蒙住眼睛的机器人一样。