Язык ассемблера - Обработка чисел
Обработка чисел в языке ассемблера включает сочетание преобразования систем счисления, преобразования ASCII и арифметических операций. В этой главе подробно объясняется, как вводить и выводить числа в различных системах счисления в программе.
Представление чисел
Внутри компьютера все числа хранятся в двоичном виде, но в программе они могут быть представлены в различных системах счисления:
| Система счисления | Основание | Синтаксис NASM | Диапазон чисел (32-битный) |
|---|---|---|---|
| Binary | 2 | 0b10101010или10101010b | 0 и 1 |
| Восьмеричная | 8 | 0o52или52o | 0-7 |
| Decimal | 10 | 42 | 0-9 |
| Шестнадцатеричная | 16 | 0x2Aили2Ah | 0-9, A-F |
Связь между ASCII и числами
Всё, что вводится и выводится на экране, — этоASCII-символы, а не двоичные числовые значения.
Понимание преобразования между символами и числовыми значениями — основа числовой обработки в ассемблере:
| Символ | Значение ASCII | Соответствующее числовое значение |
|---|---|---|
| '0' | 0x30(48) | 0 |
| '1' | 0x31(49) | 1 |
| '9' | 0x39(57) | 9 |
| 'A' | 0x41(65) | 10 (в шестнадцатеричной системе) |
| 'F' | 0x46(70) | 15 (в шестнадцатеричной системе) |
| 'a' | 0x61(97) | 10 (в шестнадцатеричной системе) |
Преобразование символа в число:数值 = ASCII码 - '0'(i.e.ASCII码 - 0x30)
Преобразование числа в символ:ASCII码 = 数值 + '0'(i.e.数值 + 0x30)
Ввод чисел (из строки в числовое значение)
То, что вводит пользователь, — это последовательность символов, которую перед использованием в вычислениях необходимо преобразовать в двоичное числовое значение:
Пример
; Преобразование строки десятичных цифр в целое число
section .data
input_str db '12345', 0 ; Входная строка "12345"
input_len dd 5 ; Длина строки
section .bss
result_val resd 1 ; Здесь хранится преобразованное числовое значение
section .text
global _start
_start:
; Алгоритм преобразования: result = 0
; result = result * 10 + (текущий символ - '0')
mov esi, input_str ; esi указывает на входную строку
mov ecx, [input_len] ; ecx = счётчик цикла
mov eax, 0 ; eax = накопленный результат
convert_loop:
mov ebx, 10
mul ebx ; eax = eax * 10
; mul ebx: edx:eax = eax * ebx
mov bl, [esi] ; Прочитать текущий символ
sub bl, '0' ; Преобразовать в числовой вид: символ - '0'
movzx ebx, bl ; Расширить ebx нулями (bl -> ebx)
add eax, ebx ; eax = eax + значение текущей цифры
inc esi ; Перейти к следующему символу
loop convert_loop
; eax теперь = 12345
mov [result_val], eax ; Сохранить результат
mov eax, 1
mov ebx, 0
int 0x80
Анализ алгоритма: рассмотрим "12345" в качестве примера:
Раунд 1: result = 0×10 + 1 = 1
Раунд 2: result = 1×10 + 2 = 12
Раунд 3: result = 12×10 + 3 = 123
...Наконец, мы получаем 12345.
Вывод чисел (числовое значение в строку)
Преобразуйте двоичное числовое значение в отображаемую десятичную строку:
Пример
; Преобразовать целочисленное значение в десятичную строку и вывести
section .data
number dd 12345 ; Число для вывода
newline db 0xA
section .bss
output_buf resb 12 ; Выходной буфер (максимальное 32-битное целое — 10 цифр + знак + null)
section .text
global _start
_start:
mov eax, [number] ; Загрузить число
mov edi, output_buf + 11 ; edi указывает на конец буфера
mov byte [edi], 0 ; нулевой терминатор (для удобства отладки)
dec edi
mov ebx, 10 ; Делитель = 10
mov ecx, 0 ; Счётчик цифр
convert_digit:
mov edx, 0 ; Очистить edx (div требует edx:eax)
div ebx ; eax = eax/10, edx = eax%10
add dl, '0' ; Преобразовать остаток в символ
mov [edi], dl ; Сохранить в буфер (с конца в начало)
dec edi ; Переместить указатель буфера вперёд
inc ecx ; Счётчик цифр +1
cmp eax, 0 ; Частное равно 0?
jne convert_digit ; Нет, продолжить цикл
; Теперь edi+1 указывает на первый допустимый символ цифры
inc edi ; Исправить указатель, чтобы он указывал на начало строки
; Вычислить длину допустимых символов
; output_buf + 11 - edi — это допустимая длина
; Вывести число
mov eax, 4
mov ebx, 1
mov ecx, edi ; Указать на преобразованную строку
; Вычислить длину
mov edx, output_buf + 11
sub edx, edi ; edx = конец буфера - начало строки
int 0x80
; Вывод новой строки
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Результат выполнения:
$ nasm -f elf32 int_to_string.asm -o int_to_string.o $ ld -m elf_i386 int_to_string.o -o int_to_string $ ./int_to_string 12345
Шестнадцатеричный ввод и вывод
Работа с шестнадцатеричными числами требует обработки как 0-9, так и преобразования A-F/a-f:
Пример
; Вывести значение в шестнадцатеричном формате
section .data
number dd 0x1A2B3C4D ; Значение для вывода
hex_prefix db '0x'
hex_prefix_len equ $ - hex_prefix
newline db 0xA
section .bss
hex_buf resb 10 ; 8 шестнадцатеричных цифр + префикс + null
section .text
global _start
_start:
; Вывести префикс "0x"
mov eax, 4
mov ebx, 1
mov ecx, hex_prefix
mov edx, hex_prefix_len
int 0x80
mov eax, [number] ; Значение для преобразования
mov edi, hex_buf + 8 ; Конец буфера (8 шестнадцатеричных цифр)
mov ecx, 8 ; Цикл 8 раз (32 бита = 8 шестнадцатеричных цифр)
hex_loop:
mov edx, 0 ; Подготовка к делению
mov ebx, 16 ; Деление на 16
div ebx ; Теперь: eax делится на ebx
; Примечание: div ebx здесь на самом деле edx:eax / ebx
; Остаток в edx (0-15), частное в eax
mov ebx, eax ; Сохранить частное
; Преобразовать остаток в шестнадцатеричный символ
mov al, dl
cmp al, 10
jl digit_09 ; 0-9
add al, 'A' - 10 ; A-F
jmp store_char
digit_09:
add al, '0' ; 0-9
store_char:
dec edi
mov [edi], al
mov eax, ebx ; Восстановить частное
loop hex_loop
; Убран цикл, напрямую используется метод декремента
; Вывод шестнадцатеричных цифр (8 цифр)
mov eax, 4
mov ebx, 1
mov ecx, hex_buf
mov edx, 8
int 0x80
; Вывод новой строки
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Полный пример: простой калькулятор сложения
Введите два однозначных числа с клавиатуры, вычислите их сумму и выведите:
Пример
; Ввод двух однозначных чисел (0-9), вычисление суммы и вывод
section .data
prompt1 db 'Введите первое число (0-9): '
prompt1_len equ $ - prompt1
prompt2 db 'Введите второе число (0-9): '
prompt2_len equ $ - prompt2
result_msg db 'Сумма = '
result_msg_len equ $ - result_msg
newline db 0xA
section .bss
num1 resb 2 ; Ввод первого числа (1 цифра + перевод строки)
num2 resb 2 ; Ввод второго числа
result_str resb 3 ; Строка результата (до 2 цифр + перевод строки)
section .text
global _start
_start:
; Приглашение для ввода первого числа
mov eax, 4
mov ebx, 1
mov ecx, prompt1
mov edx, prompt1_len
int 0x80
; Прочитать первое число
mov eax, 3
mov ebx, 0
mov ecx, num1
mov edx, 2
int 0x80
; Запрос на ввод второго числа
mov eax, 4
mov ebx, 1
mov ecx, prompt2
mov edx, prompt2_len
int 0x80
; Прочитать второе число
mov eax, 3
mov ebx, 0
mov ecx, num2
mov edx, 2
int 0x80
; Вычислить сумму
mov al, [num1] ; Прочитать первое число (символ ASCII)
sub al, '0' ; Преобразовать в числовое значение
mov bl, [num2] ; Прочитать второе число
sub bl, '0' ; Преобразовать в числовое значение
add al, bl ; Сумма
; Преобразовать результат в строку
mov ah, 0 ; Подготовка к делению
mov bl, 10 ; Разделить на 10
div bl ; al = цифра десятков, ah = цифра единиц
; Сохранить результат
add al, '0' ; Преобразовать цифру десятков в символ
mov [result_str], al ; Сохранить цифру десятков
add ah, '0' ; Преобразовать цифру единиц в символ
mov [result_str + 1], ah ; Сохранить цифру единиц
mov byte [result_str + 2], 0xA ; Перевод строки
; Вывод сообщения с результатом
mov eax, 4
mov ebx, 1
mov ecx, result_msg
mov edx, result_msg_len
int 0x80
; Вывод результата вычисления (может состоять из 1 или 2 цифр)
mov eax, 4
mov ebx, 1
mov ecx, result_str
cmp byte [result_str], '0'
je output_one_digit ; Если разряд десятков равен '0', выводить только разряд единиц
mov edx, 3 ; 2 цифры + перевод строки
jmp do_output
output_one_digit:
inc ecx ; Пропустить разряд десятков '0'
mov edx, 2 ; 1 цифра + перевод строки
do_output:
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Результат выполнения:
$ nasm -f elf32 simple_calc.asm -o simple_calc.o $ ld -m elf_i386 simple_calc.o -o simple_calc $ ./simple_calc Enter first number (0-9): 7 Enter second number (0-9): 8 Sum = 15
Другие расширенияВ ассемблере обработка числового ввода/вывода требует чёткого понимания ASCII-кодировки и преобразования систем счисления. Преобразование числа в строку (int в string) и строки в число (string в int) — это две самые базовые служебные функции. После написания рекомендуется сохранить их как шаблоны для дальнейшего прямого повторного использования.