在汇编中为什么不移位就不能打印十六进制数?
In assembly why can't a hexadecimal number be printed without shifting?
此代码来自在线示例。
假设我有要在我的 DL 中打印的变量。
DISPLAY_HEX PROC NEAR
MOV BL,DL
MOV BH,0
MOV CL,4
SHL BX,CL
MOV DL,BH
CALL ONE_DIGIT
MOV CL,4
SHR BL,CL
MOV DL,BL
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
ONE_DIGIT PROC NEAR
CMP DL,9
JA LETTER
ADD DL,48
JMP NEXT
LETTER: ADD DL, 'A'-10
NEXT: MOV AH,02H
INT 21H
END: RET
ONE_DIGIT ENDP
为什么要轮班?它不能像小数一样打印吗?
另外,为什么这里同时使用 SHR 和 SHL?
在 base16(十六进制)中,您有 16 个可能的数字 (0..F
),因此正好需要 4 位来表示一个十六进制数字 (log2(16) == 4)。这里我说的是值意义上的数字(0..F
,或 base10 中的 0..15
),而不是 ASCII 字符。
所以一个字节可以容纳两个十六进制数字。假设 DL
包含以下位:XXXXYYYY
(其中每个 X
和 Y
是二进制 0 或 1)。
首先将16位寄存器BX
左移4位。 BX
由 BL
(最低有效字节)和 BH
(最高有效字节)组成。 BH
已设置为 0,并且 BL
包含输入,因此在移位之前 BX
将包含位 00000000XXXXYYYY
。转换后它将包含 0000XXXXYYYY0000
.
然后 BX
的最高有效字节(即 BH
,现在包含 0000XXXX
)被移动到 DL
,转换为字符,并打印。
对于现在包含 YYYY0000
的第二部分 BL
,向右移动 4 位,得到 0000YYYY
。然后将该值转换为字符并打印。
您的代码过于复杂,难怪令人困惑。它是通过左移BX得到DL的高四位字节,所以这两个半字节被拆分为BH和BL,但是BL中的四位字节在前4个字节。
您需要移位一次才能将高 4 位向下移至寄存器的底部。
使用 AND 只保留低 4 位会更容易,并且 在真正的 8086 上要快得多(与现代 CPU 不同,每个移位计数都需要一个时钟周期带有可以在 1 个时钟周期内进行任意移位的桶形移位器 ALU。
这是一个更简单、更容易理解的实现,它也更紧凑,因此在真正的 8086 上更快更好。
; Input in DL
; clobbers AX, CL, DX
DISPLAY_HEX PROC NEAR
mov dh, dl ; save a copy for later
mov cl, 4
shr dl, cl ; extract the high nibble into an 8-bit integer
CALL ONE_DIGIT
and dh, 0Fh ; clear the high nibble, keeping only the low nibble
mov dl, dh ; and pass it to our function
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
为了节省代码大小,mov dl, 0Fh
/ and dl, dh
每条指令只有 2 个字节,而不是 and dh, 0Fh
的 3 个字节,并且它减少了 mov
乱序执行的关键路径。
ONE_DIGIT
的实现也有不必要的复杂分支。 (通常你会通过查找 table 实现 nibble -> ASCII,但它只需要一个分支,而不是两个。运行 额外的 add
比额外的 jmp
.)
; input: DL = 0..15 integer
; output: AL = ASCII hex char written
; clobbers = AH
ONE_DIGIT PROC NEAR
CMP DL,9
JBE DIGIT
ADD DL, 'A'-10 - '0'
DIGIT:
ADD DL, '0'
MOV AH,02H
INT 21H ; write char to stdout, return in AL. Checks for ^c/break
RET
ONE_DIGIT ENDP
我们本可以 ADD dx, '00'
(并更改 cmp
)同时对两个半字节进行操作(在 DH 和 DL 中将它们分别隔离之后)。
我们还可以提升 MOV AH,02H
,因为 int 21h
/ah=2
doesn't modify AH.。
如果我们关心性能,我们会创建一个 2 个字符的字符串并使用一个 int 21h
系统调用来同时打印两个数字。
此代码来自在线示例。 假设我有要在我的 DL 中打印的变量。
DISPLAY_HEX PROC NEAR
MOV BL,DL
MOV BH,0
MOV CL,4
SHL BX,CL
MOV DL,BH
CALL ONE_DIGIT
MOV CL,4
SHR BL,CL
MOV DL,BL
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
ONE_DIGIT PROC NEAR
CMP DL,9
JA LETTER
ADD DL,48
JMP NEXT
LETTER: ADD DL, 'A'-10
NEXT: MOV AH,02H
INT 21H
END: RET
ONE_DIGIT ENDP
为什么要轮班?它不能像小数一样打印吗? 另外,为什么这里同时使用 SHR 和 SHL?
在 base16(十六进制)中,您有 16 个可能的数字 (0..F
),因此正好需要 4 位来表示一个十六进制数字 (log2(16) == 4)。这里我说的是值意义上的数字(0..F
,或 base10 中的 0..15
),而不是 ASCII 字符。
所以一个字节可以容纳两个十六进制数字。假设 DL
包含以下位:XXXXYYYY
(其中每个 X
和 Y
是二进制 0 或 1)。
首先将16位寄存器BX
左移4位。 BX
由 BL
(最低有效字节)和 BH
(最高有效字节)组成。 BH
已设置为 0,并且 BL
包含输入,因此在移位之前 BX
将包含位 00000000XXXXYYYY
。转换后它将包含 0000XXXXYYYY0000
.
然后 BX
的最高有效字节(即 BH
,现在包含 0000XXXX
)被移动到 DL
,转换为字符,并打印。
对于现在包含 YYYY0000
的第二部分 BL
,向右移动 4 位,得到 0000YYYY
。然后将该值转换为字符并打印。
您的代码过于复杂,难怪令人困惑。它是通过左移BX得到DL的高四位字节,所以这两个半字节被拆分为BH和BL,但是BL中的四位字节在前4个字节。
您需要移位一次才能将高 4 位向下移至寄存器的底部。
使用 AND 只保留低 4 位会更容易,并且 在真正的 8086 上要快得多(与现代 CPU 不同,每个移位计数都需要一个时钟周期带有可以在 1 个时钟周期内进行任意移位的桶形移位器 ALU。
这是一个更简单、更容易理解的实现,它也更紧凑,因此在真正的 8086 上更快更好。
; Input in DL
; clobbers AX, CL, DX
DISPLAY_HEX PROC NEAR
mov dh, dl ; save a copy for later
mov cl, 4
shr dl, cl ; extract the high nibble into an 8-bit integer
CALL ONE_DIGIT
and dh, 0Fh ; clear the high nibble, keeping only the low nibble
mov dl, dh ; and pass it to our function
CALL ONE_DIGIT
RET
DISPLAY_HEX ENDP
为了节省代码大小,mov dl, 0Fh
/ and dl, dh
每条指令只有 2 个字节,而不是 and dh, 0Fh
的 3 个字节,并且它减少了 mov
乱序执行的关键路径。
ONE_DIGIT
的实现也有不必要的复杂分支。 (通常你会通过查找 table 实现 nibble -> ASCII,但它只需要一个分支,而不是两个。运行 额外的 add
比额外的 jmp
.)
; input: DL = 0..15 integer
; output: AL = ASCII hex char written
; clobbers = AH
ONE_DIGIT PROC NEAR
CMP DL,9
JBE DIGIT
ADD DL, 'A'-10 - '0'
DIGIT:
ADD DL, '0'
MOV AH,02H
INT 21H ; write char to stdout, return in AL. Checks for ^c/break
RET
ONE_DIGIT ENDP
我们本可以 ADD dx, '00'
(并更改 cmp
)同时对两个半字节进行操作(在 DH 和 DL 中将它们分别隔离之后)。
我们还可以提升 MOV AH,02H
,因为 int 21h
/ah=2
doesn't modify AH.。
如果我们关心性能,我们会创建一个 2 个字符的字符串并使用一个 int 21h
系统调用来同时打印两个数字。