【问题标题】:How to display hex numbers greater than 15 (0Fh) without using printf?如何在不使用 printf 的情况下显示大于 15(0Fh)的十六进制数?
【发布时间】:2024-05-02 13:35:02
【问题描述】:

所以,这是一个烦人的问题: [最后一行包含问题,其余内容是更好理解需求的背景。]

在汇编中,我们可以很容易地使用 printf 函数来显示内容。它会自动将二进制数据正确地转换为字符。

现在,在我的作业中,我需要使用十六进制数字并准确显示 10 个此类数字的总和。 我已经使用代码完成了对 ascii 的分类和转换为十六进制数字(存储在半字节/4 位中):

免责声明:

1) esi 寄存器包含必须保存数字的内存位置。

2) temp 是一个 5 字节的缓冲区,最多可以输入 4 个字符。

in:             ; loop to take one multidigit input

mov ebx, 1      ;set error flag
scan temp, 5    ;take input
mov ecx, [temp]
mov edi, 4      ;assumes 4 digit input, works for 0 to 4 digit input

test:           ;a validation check for input!!
cmp cl, 0Dh     ;if current byte contains '\r'
je hop          ;skip it

test0:
cmp cl, 030H    ;cl >= '0'
jge test9       ;if yes, check if cl is digit
xor ebx, ebx    ;otherwise, set error signal
jmp cont

test9:
cmp cl, 039h    ;cl <= '9'
jle cont        ;if yes, cl is a digit, case closed
jmp test10c     ;otherwise, check if cl has hex character

test10c:        ;for caps 'A'
cmp cl, 041h    ;cl >= 10 (in caps char hex)
jge test15c     ;if yes, check if cl is a caps hex character
xor ebx, ebx    ;otherwise, set error signal
jmp cont

test15c:
cmp cl, 046h    ;cl <= 15 (in hex)
jle conv        ;if yes, cl is a caps hex character, case closed
jmp test10s     ;otherwise check if cl has small hex character

test10s:
cmp cl, 061h    ;cl >= 10 (in small char hex)
jge test15s     ;if yes, check if cl is a small hex character
xor ebx, ebx    ;otherwise, set error signal
jmp cont

test15s:
cmp cl, 066h    ;cl <= 15 (in small char hex)
jle cont        ;if yes, cl is a small hex character, case closed
xor ebx, ebx    ;otherwise, set error signal

cont:
cmp ebx, 1      ;check for error
jne err         ;if error, jump to err (abort loop)
jmp hop         ;otherwise continue to next ascii character entered

hop:
rol ecx, 8      ;roll next character into cl
dec edi         ;there are only 4 roll-in operations until you repeat the number
jnz test        ;loop back to testing

end:
mov dword[temp], ecx    ;store number between call
call conv               ;convert number
mov ecx, [temp]         ;take converted number
mov dword[esi], ecx     ;store it in array
mov dword[temp], 0      ;store success signal
jmp quit

err:
mov dword[temp], 0FFh   ;store error signal

quit:                   ;return to caller
ret

conv:           ; loop to convert one multidigit input to specific format 
mov ecx, dword[temp]    ;take number
mov edi, 4              ;set number of rotations

fix:
cmp cl, 0Dh     ;check with '\r'
mov cl, 0       ;'\r' will be stored as 0, the numbers 0-15 will be stored as 1-16
jmp skip
cmp cl, 046h    ;check with 'F'
jle digitize    ;if less, convert caps character to digit
sub cl, 020h    ;if more, convert small character to caps
digitize:
cmp cl, 039h    ;check with '9'
jle norm        ;if less, convert ascii digit to normal digit
sub cl, 07h     ;if more, convert character to ascii digit
norm:
sub cl, 030h    ;convert ascii digit to hex digit
add cl, 01h     ;encode 0-15 as 1-16 to accomodate '\r'

skip:
rol ecx, 8      ;roll next character into cl
dec edi         ;there are only 4 roll-in operations until you repeat the number
jnz fix

ret

这里是以特定方式对数字进行编码。您可能会建议以其他方式增加加法算法的简单性。

最后,引用问题:

"你如何使用 sys_write(function no. 4) 将这些编码数字的总和正确写入 stdout(文件描述符 1)?"

【问题讨论】:

  • 如果我希望将 100d 转换为十六进制,我会使用计算器......另一种方式,6 余数 4。我将余数存储在数组中最后一个可用位置,然后检查结果是否大于 15。如果是,请重复。如果没有,请将 6 放入阵列的倒数第二个位置。你知道,标准的基本转换的东西。要跟踪的重要事项是 (a) 您输出的位数(这样您就知道从哪里开始显示数组的内容)(b) 大于 9 的数字是 AF
  • 如果您可以使用前导零,请从总和的最高有效半字节开始,隔离这 4 位并转换为十六进制字符 ('0'..'F'),将其打印或添加到缓冲区,然后继续下一个半字节。当您处理完所有 8 个半字节(或 4 个、或 2 个或其他)时停止。如果您不想要任何前导零,则必须首先扫描最重要的非零半字节。
  • 嘿,感谢您的评论。您看,这些数字将存储为最多 4 位十六进制数。因此,最大总和将为 0F0F0F0F + 0F0F0F0F = F0F0F0F0 单个字节结果的三位可显示数字。因此,这种情况会产生逻辑错误。该程序实际上旨在运行总和小于 0F0F0F0F 或小于 252645135 的十进制数的总和(比如 N 个)@enhzflep
  • 嘿,谢谢你的评论。你能帮我写下那个算法吗?我不太明白你是如何做到这一点的......@Michael
  • 这只是一个循环,每次迭代 ROL 4 位,然后使用按位与隔离要处理的 4 位。

标签: assembly x86 nasm


【解决方案1】:

虽然您似乎是在 linux 下进行编码,但我选择提供一个用于十六进制格式的 32 位数字打印的例程,我刚刚在 dos-box 环境中拼凑了该例程。您需要使用与int 0x10, func E 不同的机制来打印输出的每个字符。您还需要更改堆栈帧的内容以允许推入堆栈的变量默认为 4 个字节而不是 2 个字节。即 bp+4 (bp+2*2) 变为 bp+8 (bp+ 2*4) 和 ret 4 (ret 2*2) 变成 ret 8 (ret 2*4)

您也可以选择忘记手动担心加法溢出和进位标志 - 处理器不需要为如此琐碎的事情而手持。

xor ebx, ebx  ; total = 0
mov eax, 300
add ebx, eax  ; total = total + 300
mov eax, 200
add ebx, eax  ; total = total + 200
; etc, etc

这种打印功能的流程图非常短。我建议您自己(手动)绘制代码流程图,以帮助您理清思路。

一、代码:

[section .text]
[bits 16]

EntryPoint:
    push    long 0xDEADBEEF
    call    printHexLong

    ; print CRLF
    mov     ah, 0xe
    mov     al, 10
    int     0x10
    mov     al, 13
    int     0x10

    push    long 3735928559
    call    printHexLong

    ret

;void printHexLong(ulong32 num) 
printHexLong
    push    bp
    mov     bp, sp

    mov     ebx, [bp+4]         ; get the input var
    mov     cx, 8               ; number of nibbles to do
.printLoop:
    rol     ebx, 4              ; 0x11223344 becomes 0x12233441
    mov     al, bl              ; al = 0x41
    and     al, 0xF             ; al = 0x01

    cmp     al, 10              ; will this nibble be 0..9 or will it be A..F ?
    jb      .oneToNine
    sub     al, 10              ; nibble is > 9, so it's A..F  - subtract 10 so it's 0..9, 
    add     al, 'A'             ; then add ascii value of 'A'
    jmp     .outputNibble

.oneToNine:
    add     al, '0'             ; nibble is < 10, so it's a digit 0..9

.outputNibble:
    mov     ah, 0xe             ; video bios int - output a single character in teletype mode
    int     0x10                ; output the nibble

    dec     cx                  ; decrement nibble counter
    jnz     .printLoop          ; any left? If so, go again.

    pop     bp                  ; restore original value of bp
    ret     4                   ;remove the input var from the stack

现在是输出:

DEADBEEF
DEADBEEF

【讨论】:

    最近更新 更多