В качестве примера программирования процедур займёмся такой важной проблемой, как вывод на консоль чисел в различных системах счисления. Проблема эта возникает потому, что в ассемблере нет никаких специальных средств для вывода чисел, а с помощью стандартных функций можно выводить только строки.
Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.
Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:
;Процедура вывода строки на консоль
; DI - адрес строки
print_str:
push ax
mov ah,9 ;Функция DOS 09h - вывод строки
xchg dx,di ;Обмен значениями DX и DI
int 21h ;Обращение к функции DOS
xchg dx,di ;Обмен значениями DX и DI
pop ax
ret
В качестве параметра ей передаётся адрес строки в регистре DI. Строка должна оканчиваться символом ‘$’. Здесь используется команда XCHG [1], которая выполняет обмен значениями двух операндов.
Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.
;Процедура вывода конца строки (CR+LF)
print_endline:
push di
mov di,endline ;DI = адрес строки с символами CR,LF
call print_str ;Вывод строки на консоль
pop di
ret
...
endline db 13,10,'$'
Вывод чисел в двоичном виде
Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ‘0’ или ‘1’ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.
;Процедура преобразования байта в строку в двоичном виде
; AL - байт.
; DI - буфер для строки (8 символов). Значение регистра не сохраняется.
byte_to_bin_str:
push cx ;Сохранение CX
mov cx,8 ;Счётчик цикла
btbs_lp:
rol al,1 ;Циклический сдвиг AL влево на 1 бит
jc btbs_1 ;Если выдвинутый бит = 1, то переход
mov byte[di],'0' ;Добавление символа '0' в строку
jmp btbs_end
btbs_1:
mov byte[di],'1' ;Добавление символа '1' в строку
btbs_end:
inc di ;Инкремент DI
loop btbs_lp ;Команда цикла
pop cx ;Восстановление CX
ret ;Возврат из процедуры
Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:
;Процедура преобразования слова в строку в двоичном виде
; AX - слово
; DI - буфер для строки (16 символов). Значение регистра не сохраняется.
word_to_bin_str:
xchg ah,al ;Обмен AH и AL
call byte_to_bin_str ;Преобразование старшего байта в строку
xchg ah,al ;Обмен AH и AL
call byte_to_bin_str ;Преобразование младшего байта в строку
ret
И наконец вот две процедуры, которые делают то, что нужно 🙂 Буфер имеет размер 17 символов, так как в слове 16 бит + символ ‘$’, обозначающий конец строки.
;Процедура вывода байта на консоль в двоичном виде
; AL - байт
print_byte_bin:
push di
mov di,buffer ;DI = адрес буфера
call byte_to_bin_str ;Преобразование байта в AL в строку
mov byte[di],'$' ;Добавление символа конца строки
sub di,8 ;DI = адрес начала строки
call print_str ;Вывод строки на консоль
pop di
ret
;Процедура вывода слова на консоль в двоичном виде
; AX - слово
print_word_bin:
push di
mov di,buffer ;DI = адрес буфера
call word_to_bin_str ;Преобразование слова в AX в строку
mov byte[di],'$' ;Добавление символа конца строки
sub di,16 ;DI = адрес начала строки
call print_str ;Вывод строки на консоль
pop di
ret
...
buffer rb 17
Полный исходный код примера вы можете скачать отсюда: printbin.asm [2]. Результат работы программы выглядит вот так:

Вывод чисел в шестнадцатеричном виде
Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:
;Процедура преобразования числа (0-15) в шестнадцатеричную цифру
; вход : AL - число (0-15)
; выход: AL - шестнадцатеричная цифра ('0'-'F')
to_hex_digit:
add al,'0' ;Прибавляем символ '0' (код 0x30)
cmp al,'9' ;Сравнение с символом '9' (код 0x39)
jle thd_end ;Если получилось '0'-'9', то выход
add al,7 ;Прибавляем ещё 7 для символов 'A'-'F'
thd_end:
ret
Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ‘0’ (0x30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-‘F’.
Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:
;Процедура преобразования байта в строку в шестнадцатеричном виде
; AL - байт.
; DI - буфер для строки (2 символа). Значение регистра не сохраняется.
byte_to_hex_str:
push ax
mov ah,al ;Сохранение значения AL в AH
shr al,4 ;Выделение старшей тетрады
call to_hex_digit ;Преобразование в шестнадцатеричную цифру
mov [di],al ;Добавление символа в строку
inc di ;Инкремент DI
mov al,ah ;Восстановление AL
and al,0Fh ;Выделение младшей тетрады
call to_hex_digit ;Преобразование в шестнадцатеричную цифру
mov [di],al ;Добавление символа в строку
inc di ;Инкремент DI
pop ax
ret
Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:
;Процедура преобразования слова в строку в шестнадцатеричном виде
; AX - слово
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
word_to_hex_str:
xchg ah,al ;Обмен AH и AL
call byte_to_hex_str ;Преобразование старшего байта в строку
xchg ah,al ;Обмен AH и AL
call byte_to_hex_str ;Преобразование младшего байта в строку
ret
Полный исходный код примера: printhex.asm [3]. Результат работы программы выглядит вот так:

Вывод чисел в десятичном виде
С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.
Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот 🙂 Я для этого использовал стек.
В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.
;Процедура преобразования слова в строку в десятичном виде (без знака)
; AX - слово
; DI - буфер для строки (5 символов). Значение регистра не сохраняется.
word_to_udec_str:
push ax
push cx
push dx
push bx
xor cx,cx ;Обнуление CX
mov bx,10 ;В BX делитель (10 для десятичной системы)
wtuds_lp1: ;Цикл получения остатков от деления
xor dx,dx ;Обнуление старшей части двойного слова
div bx ;Деление AX=(DX:AX)/BX, остаток в DX
add dl,'0' ;Преобразование остатка в код символа
push dx ;Сохранение в стеке
inc cx ;Увеличение счетчика символов
test ax,ax ;Проверка AX
jnz wtuds_lp1 ;Переход к началу цикла, если частное не 0.
wtuds_lp2: ;Цикл извлечения символов из стека
pop dx ;Восстановление символа из стека
mov [di],dl ;Сохранение символа в буфере
inc di ;Инкремент адреса буфера
loop wtuds_lp2 ;Команда цикла
pop bx
pop dx
pop cx
pop ax
ret
Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:
;Процедура преобразования байта в строку в десятичном виде (без знака)
; AL - байт.
; DI - буфер для строки (3 символа). Значение регистра не сохраняется.
byte_to_udec_str:
push ax
xor ah,ah ;Преобразование байта в слово (без знака)
call word_to_udec_str ;Вызов процедуры для слова без знака
pop ax
ret
Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-‘, а затем инвертируем число и преобразуем как беззнаковое.
;Процедура преобразования слова в строку в десятичном виде (со знаком)
; AX - слово
; DI - буфер для строки (6 символов). Значение регистра не сохраняется.
word_to_sdec_str:
push ax
test ax,ax ;Проверка знака AX
jns wtsds_no_sign ;Если >= 0, преобразуем как беззнаковое
mov byte[di],'-' ;Добавление знака в начало строки
inc di ;Инкремент DI
neg ax ;Изменение знака значения AX
wtsds_no_sign:
call word_to_udec_str ;Преобразование беззнакового значения
pop ax
ret
;Процедура преобразования байта в строку в десятичном виде (со знаком)
; AL - байт.
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
byte_to_sdec_str:
push ax
movsx ax,al ;Преобразование байта в слово (со знаком)
call word_to_sdec_str ;Вызов процедуры для слова со знаком
pop ax
ret
Полный исходный код примера: printdec.asm [4]. Результат работы программы выглядит вот так:

Как вывести на консоль в десятичном виде очень большое число (> 32 бит) читайте здесь [5].
Вывод чисел в восьмеричном виде
Выводить числа в восьмеричном виде приходится достаточно редко, поэтому подробно описывать не буду. Можно либо делить число последовательно на 8, либо преобразовывать в цифры группы по 3 бита. Я использовал второй вариант. Смотрите код примера: printoct.asm [6]. Результат работы программы:

Вывод чисел в других системах счисления
Реализуется также, как вывод в десятичном виде — с помощью алгоритма последовательного деления на основание системы счисления. Например, если вам нужно вывести число в пятеричной системе счисления, делить надо на 5, а не на 10.
Упражнение
Напишите программу для вывода на консоль массива слов со знаком в десятичном виде (например, через запятую). Для вывода чисел можете воспользоваться моим примером или написать свою собственную процедуру. Результаты можете писать в комментариях.
Ещё раз ссылки на все примеры
- printbin.asm [2] — вывод чисел на консоль в двоичном виде
- printoct.asm [6] — вывод чисел на консоль в восьмеричном виде
- printdec.asm [4] — вывод чисел на консоль в десятичном виде (со знаком и без знака)
- printhex.asm [3] — вывод чисел на консоль в шестнадцатеричном виде