打印 Int(或 Int 到 String)

问题描述 投票:0回答:5

我正在寻找一种在汇编器中打印整数的方法(我使用的编译器是Linux上的NASM),但是,在做了一些研究之后,我一直无法找到真正可行的解决方案。我找到了用于此目的的基本算法的描述,并基于此我开发了这段代码:

global _start

section .bss
digit: resb 16
count: resb 16
i: resb 16

section .data

section .text

_start:
mov             dword[i], 108eh         ; i = 4238
mov             dword[count], 1
L01:
mov             eax, dword[i]
cdq
mov             ecx, 0Ah
div             ecx  
mov             dword[digit], edx

add             dword[digit], 30h       ; add 48 to digit to make it an ASCII char
call            write_digit

inc             dword[count]

mov             eax, dword[i]
cdq
mov             ecx, 0Ah
div             ecx  
mov             dword[i], eax 
cmp             dword[i], 0Ah  
jg              L01

add             dword[i], 48            ; add 48 to i to make it an ASCII char
mov             eax, 4                  ; system call #4 = sys_write
mov             ebx, 1                  ; file descriptor 1 = stdout
mov             ecx, i                  ; store *address* of i into ecx
mov             edx, 16                 ; byte size of 16
int             80h

jmp             exit

exit:
mov             eax, 01h                ; exit()
xor             ebx, ebx                ; errno
int             80h

write_digit:
mov             eax, 4                  ; system call #4 = sys_write
mov             ebx, 1                  ; file descriptor 1 = stdout
mov             ecx, digit              ; store *address* of digit into ecx
mov             edx, 16                 ; byte size of 16
int             80h
ret

我想要实现的 C# 版本(为了清楚起见):

static string int2string(int i)
{
    Stack<char> stack = new Stack<char>();
    string s = "";

    do
    {
        stack.Push((char)((i % 10) + 48));
        i = i / 10;
    } while (i > 10);

    stack.Push((char)(i + 48));

    foreach (char c in stack)
    {
        s += c;
    }

    return s;
}

问题在于它反向输出字符,因此对于

4238
,输出是
8324
。起初,我认为我可以使用 x86 堆栈来解决这个问题,将数字压入,然后弹出并在最后打印它们,但是当我尝试实现该功能时,它失败了,我无法再获得输出。

因此,我对如何在该算法中实现堆栈以实现我的目标(即打印整数)感到有点困惑。如果有的话,我也会对更简单/更好的解决方案感兴趣(因为它是我的第一个汇编程序之一)。

string assembly int nasm
5个回答
7
投票

一种方法是使用递归。在本例中,您将数字除以 10(得到商和余数),然后用商作为要显示的数字来调用自己;然后显示余数对应的数字。

一个例子是:

;Input
; eax = number to display

    section .data
const10:    dd 10
    section .text

printNumber:
    push eax
    push edx
    xor edx,edx          ;edx:eax = number
    div dword [const10]  ;eax = quotient, edx = remainder
    test eax,eax         ;Is quotient zero?
    je .l1               ; yes, don't display it
    call printNumber     ;Display the quotient
.l1:
    lea eax,[edx+'0']
    call printCharacter  ;Display the remainder
    pop edx
    pop eax
    ret

另一种方法是通过改变除数来避免递归。一个例子是:

;Input
; eax = number to display

    section .data
divisorTable:
    dd 1000000000
    dd 100000000
    dd 10000000
    dd 1000000
    dd 100000
    dd 10000
    dd 1000
    dd 100
    dd 10
    dd 1
    dd 0
    section .text

printNumber:
    push eax
    push ebx
    push edx
    mov ebx,divisorTable
.nextDigit:
    xor edx,edx          ;edx:eax = number
    div dword [ebx]      ;eax = quotient, edx = remainder
    add eax,'0'
    call printCharacter  ;Display the quotient
    mov eax,edx          ;eax = remainder
    add ebx,4            ;ebx = address of next divisor
    cmp dword [ebx],0    ;Have all divisors been done?
    jne .nextDigit
    pop edx
    pop ebx
    pop eax
    ret

此示例不会抑制前导零,但这很容易添加。


1
投票

我认为也许实现一个堆栈并不是最好的方法(我真的认为你可以弄清楚如何做到这一点,比如

pop
只是一个
mov
sp
的减量,因此,您实际上可以在任何您喜欢的地方设置堆栈,只需为其分配内存并将寄存器之一设置为新的“堆栈指针”)。 我认为如果您实际上为 c 风格的 null 分隔字符串分配了内存,然后创建一个函数通过您使用的相同算法将 int 转换为字符串,然后将结果传递给另一个函数,那么这段代码可以变得更清晰、更模块化能够打印这些字符串。它将避免您所遭受的一些意大利面条代码综合症,并解决您的问题。如果你想让我演示,尽管问,但如果你写了上面的内容,我想你可以弄清楚如何通过更多的拆分过程来实现。


1
投票
; Input
; EAX = pointer to the int to convert
; EDI = address of the result
; Output:
; None
int_to_string:
    xor   ebx, ebx        ; clear the ebx, I will use as counter for stack pushes
.push_chars:
    xor edx, edx          ; clear edx
    mov ecx, 10           ; ecx is divisor, devide by 10
    div ecx               ; devide edx by ecx, result in eax remainder in edx
    add edx, 0x30         ; add 0x30 to edx convert int => ascii
    push edx              ; push result to stack
    inc ebx               ; increment my stack push counter
    test eax, eax         ; is eax 0?
    jnz .push_chars       ; if eax not 0 repeat

.pop_chars:
    pop eax               ; pop result from stack into eax
    stosb                 ; store contents of eax in at the address of num which is in EDI
    dec ebx               ; decrement my stack push counter
    cmp ebx, 0            ; check if stack push counter is 0
    jg .pop_chars         ; not 0 repeat
    mov eax, 0x0a
    stosb                 ; add line feed
    ret                   ; return to main

0
投票
; eax = number to stringify/output
; edi = location of buffer

intToString:
    push  edx
    push  ecx
    push  edi
    push  ebp
    mov   ebp, esp
    mov   ecx, 10

 .pushDigits:
    xor   edx, edx        ; zero-extend eax
    div   ecx             ; divide by 10; now edx = next digit
    add   edx, 30h        ; decimal value + 30h => ascii digit
    push  edx             ; push the whole dword, cause that's how x86 rolls
    test  eax, eax        ; leading zeros suck
    jnz   .pushDigits

 .popDigits:
    pop   eax
    stosb                 ; don't write the whole dword, just the low byte
    cmp   esp, ebp        ; if esp==ebp, we've popped all the digits
    jne   .popDigits

    xor   eax, eax        ; add trailing nul
    stosb

    mov   eax, edi
    pop   ebp
    pop   edi
    pop   ecx
    pop   edx
    sub   eax, edi        ; return number of bytes written
    ret

0
投票

调试

cdq
mov             ecx, 0Ah
div             ecx  

在无符号除法

div
之前,必须将 EDX 寄存器清零。您的测试数据是正数 (4238),因此
cdq
产生 EDX=0,但请考虑如果您的 32 位整数是负数会发生什么。 EDX 将被设置为 -1 并且会发生除法溢出!我下面的解决方案考虑了负数。

cmp  dword[i], 0Ah  
jg   L01
add  dword[i], 48  ; add 48 to i to make it an ASCII char

这是错误的,因为你太早离开循环了!如果 EAX 中的商正好是 10,则仅通过添加 48 就无法生成有效的 ASCII 字符。只要商对应的十进制数字多于 1 位,您就必须继续循环。写下

jge L01

C#版本也有错误(
  } while (i > 10);
)。

mov  edx, 16       ; byte size of 16
int  80h

对于每个数字,只需 1 个字节即可传递到 Linux。指定 16 的大小是没有意义的。

解决

问题是它反向输出字符,因此对于

4238
,输出是
8324

您选择的算法从最低有效数字到最高有效数字进行工作。由于您立即输出每个数字,因此输出相反是正常的。最好不要在循环仍在运行时输出,并将数字存储在缓冲区中。然后一次打印所有数字。在下一个代码中,我将缓冲区放在堆栈上:

; IN (eax) OUT () MOD (eax,ecx,edx)
PrintInteger32:
  push ebx
  mov  ebx, 10         ; CONST divider
  mov  ecx, esp
  sub  esp, 16         ; Small local buffer

  test eax, eax
  pushf                ; (1)
  jns  .next           ; Is positive [0,2GB-1]
  neg  eax             ; Make positive [1,2GB]
.next:
  dec  ecx
  xor  edx, edx
  div  ebx             ; EDX:EAX / EBX
  add  edx, '0'        ; Remainder [0,9] -> ["0","9"]
  mov  [ecx], dl
  test eax, eax
  jnz  .next
  popf                 ; (1)
  jns  .print
  dec  ecx
  mov  byte [ecx], '-'

.print:                ; ECX is address of the MSD or the minus char
  lea  edx, [esp + 16]
  sub  edx, ecx        ; -> EDX is number of digits to print
  mov  ebx, 1          ; file descriptor 1 = stdout
  mov  eax, 4          ; system call #4 = sys_write
  int  80h
  add  esp, 16
  pop  ebx
  ret

走得更快

如果有的话,我也会对更简单/更好的解决方案感兴趣(因为它是我的第一个汇编程序之一)。

用倒数乘法代替除法运算的解决方案不一定更好,也不一定更简单,但绝对更快。 请参阅为什么 GCC 在实现整数除法时使用乘以奇怪的数字?

© www.soinside.com 2019 - 2024. All rights reserved.