NASM Assembly преобразовать ввод в целое число?

Итак, я довольно новичок в сборке, на самом деле, я очень новичок в сборке. Я написал фрагмент кода, который просто предназначен для получения числового ввода от пользователя, умножения его на 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 ответа

Вот пара функций для преобразования строк в целые числа, и наоборот:

; 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'). Также подпрограммы не обрабатывают подписанные числа. Поэтому, если вам нужны эти вещи, вам придется добавить их самостоятельно.

Основной алгоритм для строки-> цифры: total = total*10 + digit начиная с MSD. Таким образом, самая левая / самая значимая / первая цифра (в памяти и в порядке чтения) умножается в 10 N раз, где N - общее количество цифр после нее.

Делать это таким образом, как правило, эффективнее, чем умножать каждую цифру на правильную степень 10 перед добавлением. Это потребует 2 умножения; один, чтобы вырастить степень 10, а другой, чтобы применить его к цифре. (Или поиск таблицы с возрастающей степенью 10). Конечно, для эффективности вы можете использовать SSSE3 pmaddubsw и SSE2 pmaddwd умножение цифр на их местное значение параллельно: см. Как реализовать atoi с помощью SIMD?, Это, вероятно, не является победой, когда цифры, как правило, короткие. Цикл будет работать только пару раз.


В дополнение к ответу @Michael может быть полезно, чтобы функция int->string остановилась на первой нецифровой, а не на фиксированной длине. Это поймает проблемы, такие как ваша строка, включая перевод строки, когда пользователь нажал return, а также не повороты 12xy34 в очень большое количество. (Рассматривайте это как 12 как C atoi функция). Стоп-символ также может быть завершающим 0 в строке C неявной длины.

Я также сделал некоторые улучшения:

  • Не используйте медленный loop инструкция, если вы не оптимизируете для размера кода. Просто забудь, что он существует и используй dec / jnz в случаях, когда обратный отсчет до нуля - это все еще то, что вы хотите сделать, вместо сравнения указателя или чего-то еще.

  • 2 инструкции LEA значительно лучше, чем imul + add: меньшая задержка.

  • sub al,'0' экономит 1 байт sub eax,'0', но вызывает частичную регистрацию срыва на Nehalem/Core2 и еще хуже на PIII. Прекрасно подходит для всех остальных семейств процессоров, даже для Sandybridge: это RMW из AL, поэтому он не переименовывает частичный регистр отдельно от EAX.

  • накапливать результат в EAX, где мы все равно хотим его вернуть. (Если вы вместо этого вызываете это, используйте любой регистр, в котором хотите получить результат.)

Я изменил регистры так, чтобы они соответствовали x86-64 System V ABI (первый аргумент в RDI, возврат в EAX). Используйте любые регистры, которые вы хотите, и используйте 32-битные регистры, чтобы он работал в 32-битном режиме; Я не использовал 64-битный размер операнда.

    ; args: pointer in RDI to ASCII decimal digits, terminated by a non-digit
    ; clobbers: ECX, EDX
    ; returns: EAX = atoi(RDI)  (base 10 unsigned)
    ;          RDI = pointer to first non-digit
global string_to_int
string_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
  .loop_entry:
     inc     rdi
     movzx   ecx, byte [rdi]
     sub     ecx, '0'
     cmp     ecx, 9             ; cl,9 is equivalent, but doesn't save a byte; only the al,imm8 case is special.
     jbe     .next_digit        ; } while( digit <= 9 )

     ret                ; return with total in eax

Создание особой первой итерации и ее обработка перед переходом в основную часть цикла называется очисткой цикла. Очистка первой итерации позволяет нам специально оптимизировать ее, потому что мы знаем total=0, поэтому нет необходимости умножать что-либо на 10. Это похоже на начало с sum = array[0]; i=1 вместо sum=0, i=0;,

Чтобы получить красивую структуру цикла (с условной ветвью внизу), я использовал трюк - прыгнуть в середину цикла для первой итерации. Это даже не потребовало лишних jmp потому что я уже разветвлялся в очищенной первой итерации.

Простой способ решить проблему выхода из цикла для нецифрового символа - это иметь jcc в теле цикла, как if() break; заявление в C до total = total*10 + digit, Но тогда мне нужно jmp и иметь всего 2 инструкции перехода в цикле, что означает дополнительные издержки.


Если мне не нужно sub ecx, '0' результат для условия цикла, я мог бы использовать lea eax, [rax*2 + rcx - '0'] сделать это как часть LEA, а также. Но это сделало бы задержку LEA 3 цикла вместо 1, на процессорах семейства Sandybridge. (3-компонентный LEA против 2 или менее.) Два LEA образуют цепочку зависимостей, переносимых петлей eax (total), поэтому (особенно для большого количества) это не будет стоить того на Intel. На процессорах где base + scaled-index не быстрее чем base + scaled-index + disp8 ( Bulldozer-family / Ryzen), тогда, конечно, если у вас есть явная длина в качестве условия цикла и вы вообще не хотите проверять цифры.

Для получения дополнительной информации по оптимизации см. http://agner.org/optmize и другие ссылки в вики-теге x86.

В теге wiki также есть ссылки для начинающих, в том числе раздел часто задаваемых вопросов со ссылками на целочисленные функции> string и другие распространенные вопросы для начинающих. Как напечатать целое число в Программирование на уровне сборки без printf из библиотеки c?,

Другие вопросы по тегам