NASM程序集将输入转换为整数?

时间:2013-10-11 03:22:03

标签: assembly x86 char int nasm

好的,所以我对装配很新,事实上,我对装配很新。我写了一段代码,它只是意味着从用户那里获取数字输入,乘以10,并通过程序退出状态将结果表示给用户(在终端中键入echo $?) 问题是,它没有给出正确的数字,4x10显示为144.所以我认为输入可能是一个字符,而不是一个整数。我的问题是,如何将字符输入转换为整数,以便可以在算术计算中使用?

如果有人能回答我记得我是初学者会很棒:) 另外,如何将所述整数转换回字符?

section .data

section .bss
input resb 4

section .text

global _start
_start:

mov eax, 3
mov ebx, 0
mov ecx, input
mov edx, 4
int 0x80

mov ebx, 10
imul ebx, ecx

mov eax, 1
int 0x80

2 个答案:

答案 0 :(得分:8)

这里有一些将字符串转换为整数的函数,反之亦然:

; Input:
; ESI = pointer to the string to convert
; ECX = number of digits in the string (must be > 0)
; Output:
; EAX = integer value
string_to_int:
  xor ebx,ebx    ; clear ebx
.next_digit:
  movzx eax,byte[esi]
  inc esi
  sub al,'0'    ; convert from ASCII to number
  imul ebx,10
  add ebx,eax   ; ebx = ebx*10 + eax
  loop .next_digit  ; while (--ecx)
  mov eax,ebx
  ret


; Input:
; EAX = integer value to convert
; ESI = pointer to buffer to store the string in (must have room for at least 10 bytes)
; Output:
; EAX = pointer to the first character of the generated string
int_to_string:
  add esi,9
  mov byte [esi],STRING_TERMINATOR

  mov ebx,10         
.next_digit:
  xor edx,edx         ; Clear edx prior to dividing edx:eax by ebx
  div ebx             ; eax /= 10
  add dl,'0'          ; Convert the remainder to ASCII 
  dec esi             ; store characters in reverse order
  mov [esi],dl
  test eax,eax            
  jnz .next_digit     ; Repeat until eax==0
  mov eax,esi
  ret

这就是你使用它们的方式:

STRING_TERMINATOR equ 0

lea esi,[thestring]
mov ecx,4
call string_to_int
; EAX now contains 1234

; Convert it back to a string
lea esi,[buffer]
call int_to_string
; You now have a string pointer in EAX, which
; you can use with the sys_write system call

thestring: db "1234",0
buffer: resb 10

请注意,我没有在这些例程中进行太多错误检查(例如检查是否存在范围'0' - '9'之外的字符)。例程也不处理带符号的数字。因此,如果您需要这些东西,您必须自己添加它们。

答案 1 :(得分:2)

字符串>数字的基本算法是:total = total*10 + digit ,从MSD开始。所以最左边/最高有效/第一个数字(在内存中,在阅读顺序中)乘以10 N次,其中N是后面的总位数。

这样做通常比在添加之前将每个数字乘以10的正确幂更有效。这需要2次乘法;一个增加10的幂,另一个增加到数字。 (或以10的递增幂查表表。)

当然,为了提高效率,您可以使用SSSE3 pmaddubsw和SSE2 pmaddwd将数字乘以其地方值并行:请参阅How to implement atoi using SIMD?。但是,当数字通常很短时,这可能不是一场胜利。当大多数数字只有几位数时,标量循环是有效的。

添加@ Michael的答案,让int->字符串函数停在第一个非数字,而不是固定长度可能很有用。这将捕获诸如字符串之类的问题,包括用户按下返回时的换行符,以及不将12xy34转换为非常大的数字。 (将其视为12like C's atoi function)。 stop字符也可以是C隐式长度字符串中的终止0

我也做了一些改进:

  • 除非您针对代码大小进行优化,否则不要使用the slow loop instruction。只是忘记它存在并使用dec / jnz,如果仍然是你想做的倒计时,而不是比较指针或其他东西。

  • 2 LEA说明明显优于imul + add:延迟较低。

  • 将结果累积到我们想要返回它的EAX中。 (如果您将其内联而不是调用它,请使用您想要结果的任何寄存器。)

我更改了寄存器,因此它遵循x86-64 System V ABI(RDI中的第一个arg,在EAX中返回)。

移植到32位:这根本不依赖于64位;只需使用32位寄存器即可将其移植到32位。 (即将rdi替换为edirax替换为ecx,将rax替换为eax。注意32和64位之间的C调用约定差异,例如EDI是调用保留的,并且args通常在堆栈上传递。但是如果你的调用者是asm,你可以在EDI中传递一个arg。

    ; args: pointer in RDI to ASCII decimal digits, terminated by a non-digit
    ; clobbers: ECX
    ; returns: EAX = atoi(RDI)  (base 10 unsigned)
    ;          RDI = pointer to first non-digit
global base10string_to_int
base10string_to_int:

     movzx   eax, byte [rdi]    ; start with the first digit
     sub     eax, '0'           ; convert from ASCII to number
     cmp     al, 9              ; check that it's a decimal digit [0..9]
     jbe     .loop_entry        ; too low -> wraps to high value, fails unsigned compare check

     ; else: bad first digit: return 0
     xor     eax,eax
     ret

     ; skew the loop so we can put the JCC at the bottom where it belongs
     ; but still check the digit before messing up our total
  .next_digit:                  ; do {
     lea     eax, [rax*4 + rax]    ; total *= 5
     lea     eax, [rax*2 + rcx]    ; total = (total*5)*2 + digit
       ; imul eax, 10  / add eax, ecx
  .loop_entry:
     inc     rdi
     movzx   ecx, byte [rdi]
     sub     ecx, '0'
     cmp     ecx, 9
     jbe     .next_digit        ; } while( digit <= 9 )

     ret                ; return with total in eax

这会停止转换第一个非数字字符。通常这是0字节终止隐式长度字符串,但您可以在循环后检查ecx == -'0'你想检测一些其他非数字字符的结尾。如果您的输入是显式长度字符串,则需要使用循环计数器而不是检查终结符(如@ Michael的答案),因为内存中的下一个字节可能是另一个数字。或者它可能位于未映射的页面中。

使第一次迭代特殊并在跳转到循环的主要部分之前处理它被称为loop peeling。剥离第一次迭代允许我们特别优化它,因为我们知道总数= 0因此不需要将任何数字乘以10.这就像从sum = array[0]; i=1而不是{{1}开始一样}。

为了获得nice loop structure (with the conditional branch at the bottom),我使用了跳转到循环中间进行第一次迭代的技巧。这甚至不需要额外的sum=0, i=0;因为我已经在剥离的第一次迭代中分支了。

解决在非数字上退出循环问题的简单方法是在循环体中有一个jmp,就像在{{1}之前的C中的jcc语句一样}。但是,我需要一个if() break;并在循环中有2个总分支指令,这意味着更多的开销。

如果我不需要total = total*10 + digit结果作为循环条件,我可以使用jmp将其作为LEA的一部分来执行。但是在Sandybridge家族的CPU上会有made the LEA latency 3 cycles instead of 1。 (3分量LEA与2或更少。)两个LEA在sub ecx, '0'lea eax, [rax*2 + rcx - '0'])上形成一个循环承载的依赖链,所以(特别是对于大数),它不值得英特尔。在eax不快于totalBulldozer-family / Ryzen)的CPU上,如果您有明确的长度作为循环条件并且不想检查数字,请确保所有

我首先使用movzx加载零扩展,而不是在将数字从ASCII转换为整数后执行此操作。 (必须在某些时候完成添加到32位EAX中)。通常,操作ASCII数字的代码使用字节操作数大小,如base + scaled-index。但是这会在大多数CPU上产生对旧版RCX的错误依赖。

base + scaled-index + disp8mov cl, [rdi]上保存1个字节,但在Nehalem / Core2上导致部分寄存器停顿,在PIII上更糟糕。精细on all other CPU families,即使是Sandybridge:它是AL的RMW,因此它不会将部分注册表与EAX分开重命名。但是sub al,'0'不会导致问题,因为读取字节寄存器总是很好。它保存了一个字节(没有ModRM字节的特殊编码),所以我在函数的顶部使用了它。

有关更多优化内容,请参阅http://agner.org/optmize,{strong>以及 tag wiki中的其他链接。

标签维基也有初学者链接,包括一个FAQ部分,其中包含指向整数 - >字符串函数的链接,以及其他常见的初学者问题。

相关:How do I print an integer in Assembly Level Programming without printf from the c library? 与此问题相反,整数 - &gt; base10string。