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

use*_*492 3 int x86 assembly nasm char

好吧,所以我对组装很新,事实上,我对组装很新.我写了一段代码,它只是意味着从用户那里获取数字输入,乘以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
Run Code Online (Sandbox Code Playgroud)

Mic*_*ael 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
Run Code Online (Sandbox Code Playgroud)

这就是你如何使用它们:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 请不要推荐[慢速`loop`指令](/sf/ask/2501979931/) !此外,2 个 LEA 指令明显优于 `imul` + `add`:`lea ebx, [4*ebx + ebx]` (ebx*=5) / `lea ebx, [eax + 2*ebx]`。或者您是否针对代码大小进行了优化?`sub al,'0'` 比 `sub eax,'0'` 节省了 1 个字节,但在 Nehalem/Core2 上导致部分寄存器停顿,在 PIII 上更糟。(在 Sandybridge 上没问题;它是 AL 的 RMW,因此它不会将部分寄存器与 EAX 分开重命名。) (2认同)

Pet*_*des 6

string->digit 的基本算法是:total = total*10 + digit,从 MSD 开始。(例如,digit = *p++ - '0'用于 ASCII 数字字符串)。因此,最左边/最重要/第一个数字(在内存中,按阅读顺序)乘以 10 N 次,其中 N 是其后的总位数。

这样做通常比在添加之前将每个数字乘以 10 的正确幂更有效。那将需要 2 个乘法;一个是增加 10 的幂,另一个是将其应用于数字。(或以 10 的升幂进行查表)。

当然,为了提高效率,您可以使用 SSSE3pmaddubsw和 SSE2pmaddwd将数字与其位值并行相乘:请参阅如何使用 SIMD 实现 atoi?. 不过,当数字通常很短时,这可能不是一场胜利。当大多数数字只有几位数字时,标量循环是有效的。


加上@Michael 的回答,让 int->string 函数停在第一个 non-digit而不是固定长度可能很有用。这将捕获诸如您的字符串之类的问题,包括用户按下返回时的换行符,以及不会12xy34变成非常大的数字。(把它当作12就像 C 的atoi函数一样)。停止字符也可以0是 C 隐式长度字符串中的终止符。

我也做了一些改进:

  • 除非您正在优化代码大小,否则不要使用慢速loop指令。只是忘记它的存在并在倒数到零仍然是你想要做的情况下使用dec/ jnz,而不是比较指针或其他东西。

  • 2 LEA 指令明显优于imul+ add:更低的延迟。

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

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

移植到 32 位: 这根本不依赖于 64 位;只需使用 32 位寄存器即可将其移植到 32 位。(即替换rdiediraxecx,和raxeax)。注意 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

     ; rotate 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
Run Code Online (Sandbox Code Playgroud)

这将停止转换第一个非数字字符。 通常这将是终止隐式长度字符串的 0 字节。如果您想检测尾随垃圾,您可以通过检查ecx == -'0'(它仍然保存str[i] - '0'超出范围的整数“数字”值)在循环之后检查它是字符串结尾,而不是其他一些非数字字符。

如果您的输入是显式长度的字符串,则需要使用循环计数器而不是检查终止符(如@Michael 的回答),因为内存中的下一个字节可能是另一个数字。或者它可能位于未映射的页面中。


在跳转到循环的主要部分之前使第一次迭代变得特殊并处理它称为循环剥离。剥离第一次迭代允许我们特别优化它,因为我们知道 total=0 所以没有必要将任何东西乘以 10。这就像从而sum = array[0]; i=1不是开始sum=0, i=0;

为了获得漂亮的循环结构(底部有条件分支),我使用了跳到循环中间进行第一次迭代的技巧。这甚至不需要额外的费用,jmp因为我已经在剥离的第一次迭代中进行了分支。重新排序循环,使if()break中间的循环变成底部的循环分支称为循环旋转,可能涉及剥离第一次迭代的第一部分和最后一次迭代的第二部分。

解决在非数字上退出循环问题的简单方法是jcc在循环体中使用 a,就像if() break;C 中的语句total = total*10 + digit. 但是然后我需要一个jmp并且在循环中总共有 2 个分支指令,这意味着更多的开销。


如果我不需要sub ecx, '0'循环条件的结果,我也可以使用lea eax, [rax*2 + rcx - '0']它作为 LEA 的一部分。但是,在 Sandybridge 系列 CPU 上,这会使LEA 延迟为 3 个周期而不是 1 个周期。(3-component LEA vs. 2 or less.)这两个 LEA 在eax( total)上形成了一个循环携带的依赖链,所以(特别是对于大数字)它在 Intel 上是不值得的。在base + scaled-index不比base + scaled-index + disp8( Bulldozer-family / Ryzen )快的CPU 上,当然,如果您有明确的长度作为循环条件并且根本不想检查数字。

我首先使用 movzx 加载零扩展名,而不是在将数字从 ASCII 转换为整数后这样做。(必须在某个时候完成才能添加到 32 位 EAX 中)。操作 ASCII 数字的代码通常使用字节操作数大小,例如mov cl, [rdi]. 但这会在大多数 CPU 上创建对 RCX 旧值的错误依赖。

sub al,'0'节省了 1 个字节sub eax,'0',但在 Nehalem/Core2 上导致部分寄存器停顿,在 PIII 上更糟。在所有其他 CPU 系列上都很好,甚至 Sandybridge:它是 AL 的 RMW,因此它不会将部分 reg 与 EAX 分开重命名。但cmp al, 9不会引起问题,因为读取字节寄存器总是没问题的。它保存了一个字节(没有 ModRM 字节的特殊编码),所以我在函数的顶部使用了它。


欲了解更多优化的东西,看到http://agner.org/optimize而在其他环节 标签维基

标签 wiki 也有初学者链接,包括一个 FAQ 部分,其中包含指向 integer->string 函数的链接,以及其他常见的初学者问题。

有关的:


归档时间:

查看次数:

15637 次

最近记录:

6 年,11 月 前