将数据从双字数组移动到字节数组
move data from double word array to byte array
您好,我正在尝试将给定的双字数组划分为必须为字节的数组,
a dd 12345678h,1A2B3Ch,78h, ;given array
并且我只想添加不等于 0 的数字
如您所见,第一个数字没问题,第二个数字末尾有两个零
001A2B3Ch,第三个有六个零 00000078h
我编写了一个代码来执行此操作,对于第一个数字,它可以使用 78,56,34,12,28,2B 的 ASCII 代码添加到数组字符中,但对于最后两个数字,它必须是不正确的看起来像 (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
首先,始终了解您的数据,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,您只需将值中的位值移动两个位置 "up"(左)。
shl si,2 ; si *= 4 (truncated to 16 bit value)
但这会破坏原始的 si
("index"),因此人们通常不这样做而是调整循环增量。您将从 si = 0
开始,但您会使用 add si,4
而不是 inc si
。不再需要乘法了。
add bx,1
伤害了我的眼睛,我更喜欢 inc bx
在 human Assembly 中(虽然在某些代的 x86 CPU 上 add bx,1
更快,但在现代 x86 上 inc
又好了)。
mov al,byte ptr a[si]+1
是非常奇怪的语法,我更喜欢保持 "Intel-like" 简单,即。 mov al,byte ptr [si + a + 1]
。它不是 C 数组,它实际上是从括号内的地址从内存中加载值。随着时间的推移,模仿 C-array 语法可能只会让你感到困惑。 byte ptr
也可以从中删除,因为 al
已经定义了数据宽度(除非你正在使用一些 MASM,它在 dd
数组上强制执行此操作,但我不想触摸那个微软的东西有十英尺的杆子)。
同样适用于 mov n[bx],al
= mov [n + bx],al
或 mov [bx + n],al
,以代码中更有意义的为准。
但总的来说,在循环内使用索引有点不寻常,通常你想在init部分循环之前将所有索引转换为地址,并在循环内使用不进行任何计算的最终指针(按元素大小递增,即 add si,4
双字)。然后你不需要做任何索引乘法。
特别是在 16 位模式下,寻址模式非常有限,在 32/64b 模式下,您至少可以将一个寄存器与普通大小(1、2、4、8)相乘,即。 mov [n + ebx * 4],eax
= 不需要单独相乘。
编辑:在可用的 16b 模式下没有比例(乘以 "index" 部分的 1/2/4/8),可能的示例 [si*4]
将不起作用。
新变体存储来自 most-significant 双字字节的字节(即反转 x86 双字的 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
以保持简短的方式编写,而不是为了性能(我强烈建议你以同样的目标为目标,直到你对这门语言感到非常舒服为止,即使是简单的语言对于初学者来说也足够困难了没有任何 expert-trickery).
顺便说一句,您应该找到一些适合您的调试器,这样您就可以逐条执行指令并观察如何添加 "n" 中的结果值,以及为什么。或者您可能会更早注意到 bx
+si
与 mul
没有按照您的预期进行操作,其余代码在错误的索引上运行。在没有调试器的情况下使用 Assembly 编程就像试图 assemble 蒙住眼睛的机器人。
您好,我正在尝试将给定的双字数组划分为必须为字节的数组,
a dd 12345678h,1A2B3Ch,78h, ;given array
并且我只想添加不等于 0 的数字 如您所见,第一个数字没问题,第二个数字末尾有两个零 001A2B3Ch,第三个有六个零 00000078h
我编写了一个代码来执行此操作,对于第一个数字,它可以使用 78,56,34,12,28,2B 的 ASCII 代码添加到数组字符中,但对于最后两个数字,它必须是不正确的看起来像 (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
首先,始终了解您的数据,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,您只需将值中的位值移动两个位置 "up"(左)。
shl si,2 ; si *= 4 (truncated to 16 bit value)
但这会破坏原始的 si
("index"),因此人们通常不这样做而是调整循环增量。您将从 si = 0
开始,但您会使用 add si,4
而不是 inc si
。不再需要乘法了。
add bx,1
伤害了我的眼睛,我更喜欢 inc bx
在 human Assembly 中(虽然在某些代的 x86 CPU 上 add bx,1
更快,但在现代 x86 上 inc
又好了)。
mov al,byte ptr a[si]+1
是非常奇怪的语法,我更喜欢保持 "Intel-like" 简单,即。 mov al,byte ptr [si + a + 1]
。它不是 C 数组,它实际上是从括号内的地址从内存中加载值。随着时间的推移,模仿 C-array 语法可能只会让你感到困惑。 byte ptr
也可以从中删除,因为 al
已经定义了数据宽度(除非你正在使用一些 MASM,它在 dd
数组上强制执行此操作,但我不想触摸那个微软的东西有十英尺的杆子)。
同样适用于 mov n[bx],al
= mov [n + bx],al
或 mov [bx + n],al
,以代码中更有意义的为准。
但总的来说,在循环内使用索引有点不寻常,通常你想在init部分循环之前将所有索引转换为地址,并在循环内使用不进行任何计算的最终指针(按元素大小递增,即 add si,4
双字)。然后你不需要做任何索引乘法。
特别是在 16 位模式下,寻址模式非常有限,在 32/64b 模式下,您至少可以将一个寄存器与普通大小(1、2、4、8)相乘,即。 mov [n + ebx * 4],eax
= 不需要单独相乘。
编辑:在可用的 16b 模式下没有比例(乘以 "index" 部分的 1/2/4/8),可能的示例 [si*4]
将不起作用。
新变体存储来自 most-significant 双字字节的字节(即反转 x86 双字的 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
以保持简短的方式编写,而不是为了性能(我强烈建议你以同样的目标为目标,直到你对这门语言感到非常舒服为止,即使是简单的语言对于初学者来说也足够困难了没有任何 expert-trickery).
顺便说一句,您应该找到一些适合您的调试器,这样您就可以逐条执行指令并观察如何添加 "n" 中的结果值,以及为什么。或者您可能会更早注意到 bx
+si
与 mul
没有按照您的预期进行操作,其余代码在错误的索引上运行。在没有调试器的情况下使用 Assembly 编程就像试图 assemble 蒙住眼睛的机器人。