在汇编中为什么不移位就不能打印十六进制数?

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

为什么要轮班?它不能像小数一样打印吗? 另外,为什么这里同时使用 SHRSHL

在 base16(十六进制)中,您有 16 个可能的数字 (0..F),因此正好需要 4 位来表示一个十六进制数字 (log2(16) == 4)。这里我说的是值意义上的数字(0..F,或 base10 中的 0..15),而不是 ASCII 字符。

所以一个字节可以容纳两个十六进制数字。假设 DL 包含以下位:XXXXYYYY(其中每个 XY 是二进制 0 或 1)。

首先将16位寄存器BX左移4位。 BXBL(最低有效字节)和 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 系统调用来同时打印两个数字。