Assembly 在汇编中为什么可以';十六进制数字不能不移位地打印吗?
此代码取自一个在线示例。 假设我有一个要在DL中打印的变量Assembly 在汇编中为什么可以';十六进制数字不能不移位地打印吗?,assembly,emu8086,Assembly,Emu8086,此代码取自一个在线示例。 假设我有一个要在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 D
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
将包含位00000000 xxxxyyyy
。移位后,它将包含000xxxxyyyy0000
然后将
BX
(即BH
,现在包含0000XXXX
)的最高有效字节移动到DL
,转换为字符并打印
对于现在包含
YYYY0000
的第二部分BL
,将其向右移位4位,从而产生0000yyyyy
。然后将该值转换为字符并打印 您的代码过于复杂,难怪会令人困惑。它通过左移BX获得DL的高半字节,因此两个半字节被分为BH和BL,但BL中的半字节位于前4个字节中
您需要一次移位才能将高4位降到寄存器的底部
在真正的8086上,仅使用和保持低位4位会更容易,速度也会更快(在8086上,移位的每个计数都需要一个时钟周期,不像现代CPU中的桶形移位器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
/和dl,dh
每条指令只有2个字节,而不是和dh,0Fh
的3个字节,并且它使mov
脱离了无序执行的关键路径
一位
的实现也有不必要的复杂分支。(通常您会使用查找表实现半字节->ASCII,但它只需要一个分支,而不是两个分支。运行额外的add
比额外的jmp
便宜)
我们本可以将dx,'00'(并更改cmp
)添加到一次操作两个半字节(在DH和DL中分别隔离后)
我们还可以提升MOV-AH,02H
,因为
如果我们关心性能,我们会创建一个两个字符的字符串,并使用一个
int21h
系统调用同时打印两个数字。您也不能直接打印小数。您需要转换为文本。恰好每4位是一个十六进制数字,所以可以使用移位而不是通常使用的除法。快速一看,代码是错误的,因为它将同一个数字打印两次。但是我还没有喝咖啡;)啊,好的,它可以很好地打印两位数字,shr
是撤销shl
,这样低4位就回到了开始的位置。谢谢你,杰斯特,我想我明白了。哇!非常感谢你花时间解释,太棒了!
; 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