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?,