Assembler
Изучение с «нуля»
суббота, 6 июня 2009 г.
Данные сложного типа. Массивы.
Массив — структурированный тип данных, состоящий из некоторого числа элементов одного типа.
Для того чтобы разобраться в возможностях и особенностях обработки массивов в программах на ассемблере, нужно ответить на следующие вопросы:
- Как описать массив в программе?
- Как инициализировать массив, то есть как задать начальные значения его элементов?
- Как организовать доступ к элементам массива?
- Как организовать массивы с размерностью более одной?
- Как организовать выполнение типовых операций с массивами?
Описание и инициализация массива в программе
Специальных средств описания массивов в программах ассемблера, конечно, нет. При необходимости использовать массив в программе его нужно моделировать одним из следующих способов:
1) Перечислением элементов массива в поле операндов одной из директив описания данных. При перечислении элементы разделяются запятыми. К примеру:
;массив из 5 элементов.Размер каждого
элемента 4 байта:
mas dd 1,2,3,4,5
2) Используя оператор повторения dup. К примеру:
;массив из 5 нулевых элементов.
;Размер каждого элемента 2 байта:
mas dw 5 dup (0)
Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива.
3) Используя директивы label и rept . Пара этих директив может облегчить описание больших массивов в памяти и повысить наглядность такого описания. Директива rept относится к макросредствам языка ассемблера и вызывает повторение указанное число раз строк, заключенных между директивой и строкой endm . К примеру, определим массив байт в области памяти, обозначенной идентификатором mas_b. В данном случае директива label определяет символическое имя mas_b, аналогично тому, как это делают директивы резервирования и инициализации памяти. Достоинство директивы label в том, что она не резервирует память, а лишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив label, записанных одна за другой, можно присвоить одной и той же области памяти разные имена и разный тип, что и сделано в следующем фрагменте:
.
n=0
.
mas_b label byte
mas_w label word
rept 4
dw 0f1f0h
endm
В результате в памяти будет создана последовательность из четырех слов f1f0. Эту последовательность можно трактовать как массив байт или слов в зависимости от того, какое имя области мы будем использовать в программе — mas_b или mas_w.
4) Использование цикла для инициализации значениями области памяти, которую можно будет впоследствии трактовать как массив.
Посмотрим на примере, каким образом это делается.
;prg_12_1.asm
MASM
MODEL small
STACK 256
.data
mes db 0ah,0dh,’Массив- ‘,’$’
mas db 10 dup (?) ;исходный массив
i db 0
.code
main:
mov ax,@data
mov ds,ax
xor ax,ax ;обнуление ax
mov cx,10 ;значение счетчика цикла в cx
mov si,0 ;индекс начального элемента в cx
go: ;цикл инициализации
mov bh,i ;i в bh
mov mas[si],bh ;запись в массив i
inc i ;инкремент i
inc si ;продвижение к следующему
;элементу массива
loop go ;повторить цикл
;вывод на экран получившегося массива
mov cx,10
mov si,0
mov ah,09h
lea dx,mes
int 21h
show:
mov ah,02h ;функция вывода значения
;из al на экран
mov dl,mas[si]
add dl,30h ;преобразование числа в символ
int 21h
inc si
loop show
exit:
mov ax,4c00h ;стандартный выход
int 21h
end main ;конец программы
Доступ к элементам массива
При работе с массивами необходимо четко представлять себе, что все элементы массива располагаются в памяти компьютера последовательно.
Само по себе такое расположение ничего не говорит о назначении и порядке использования этих элементов. И только лишь программист с помощью составленного им алгоритма обработки определяет, как нужно трактовать эту последовательность байт, составляющих массив. Так, одну и ту же область памяти можно трактовать как одномерный массив, и одновременно те же самые данные могут трактоваться как двухмерный массив. Все зависит только от алгоритма обработки этих данных в конкретной программе. Сами по себе данные не несут никакой информации о своем “смысловом”, или логическом, типе. Помните об этом принципиальном моменте.
Эти же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает об их существовании и ему абсолютно все равно, каковы их численные смысловые значения.
Для того чтобы локализовать определенный элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны позаботиться и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работают особым образом. Другими словами, когда при программировании на ассемблере мы говорим об индексе, то скорее подразумеваем под этим не номер элемента в массиве, а некоторый адрес.
Давайте еще раз обратимся к описанию массива. К примеру, в программе статически определена последовательность данных:
dim dw 0011h,2233h,4455h,6677h,8899h
Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw , то есть она равна 2 байта . Чтобы получить доступ к числу 6677h, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса этого элемента на размер элемента массива:
база + (индекс*размер элемента)
Архитектура микропроцессора предоставляет достаточно удобные программно-аппаратные средства для работы с массивами. К ним относятся базовые и индексные регистры, позволяющие реализовать несколько режимов адресации данных. Используя данные режимы адресации, можно организовать эффективную работу с массивами в памяти.
Для более глубокого понимания можно помедитировать на эту програмку:
Эта программа ни чего не выводит на экран. Предназначена просто для медитации на нее под отладчиком. Внимание. При отладке, чтобы увидеть работу цикла, на команде loop go, нужно нажимать клавишу F7.
Зарисовка из медитации на эту прогу:
Двухмерные массивы
С представлением одномерных массивов в программе на ассемблере и организацией их обработки все достаточно просто. А как быть если программа должна обрабатывать двухмерный массив? Все проблемы возникают по-прежнему из-за того, что специальных средств для описания такого типа данных в ассемблере нет. Двухмерный массив нужно моделировать. На описании самих данных это почти никак не отражается — память под массив выделяется с помощью директив резервирования и инициализации памяти.
Непосредственно моделирование обработки массива производится в сегменте кода, где программист, описывая алгоритм обработки ассемблеру, определяет, что некоторую область памяти необходимо трактовать как двухмерный массив.
При этом вы вольны в выборе того, как понимать расположение элементов двухмерного массива в памяти: по строкам или по столбцам.
Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i, j) вычисляется по формуле
(база + количество_элементов_в_строке * размер_элемента * i+j)
Здесь i = 0. n–1 указывает номер строки, а j = 0. m–1 указывает номер столбца.
Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 на 4
(i= 0. 3, j = 0. 3):
23 04 05 67
05 06 07 99
67 08 09 23
87 09 00 08
В памяти элементы этого массива будут расположены в следующей последовательности:
23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08
Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент
mas(2, 3) = 23, то проведя нехитрый подсчет, убедимся в правильности наших рассуждений:
Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11
Посмотрите на представление массива в памяти и убедитесь, что по этому смещению действительно находится нужный элемент массива. Прога для медитации:
Эта программа, тоже ни чего не выводит, так как предназначена для выполнения ее под отладчиком, для пущего научения.
Источник
20. Вывод чисел на консоль в ассемблере i8086
Архитектура ЭВМ и систем › Язык Assembler › i8086 › 20. Вывод чисел на консоль в ассемблере i8086
- В этой теме 2 ответа, 1 участник, последнее обновление 1 год, 3 месяца назад сделано .
Статья основана на материале xrnd с сайта asmworld (из учебного курса по программированию на ассемблер 16-битного процессора 8086 под DOS).
В качестве примера программирования процедур займёмся такой важной проблемой, как вывод на консоль чисел в различных системах счисления. Проблема эта возникает потому, что в ассемблере нет никаких специальных средств для вывода чисел, а с помощью стандартных функций можно выводить только строки.
Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.
Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:
В качестве параметра ей передаётся адрес строки в регистре DI. Строка должна оканчиваться символом ‘$’. Здесь используется команда XCHG , которая выполняет обмен значениями двух операндов.
Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.
Вывод чисел в двоичном виде
Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ‘0’ или ‘1’ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.
Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:
И наконец вот две процедуры, которые делают то, что нужно. Буфер имеет размер 17 символов, так как в слове 16 бит + символ ‘$’ , обозначающий конец строки.
Результат работы программы выглядит вот так:
Вывод чисел в шестнадцатеричном виде
Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:
Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ‘0’ (0x30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-‘F’.
Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:
Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:
Результат работы программы выглядит вот так:
Вывод чисел в десятичном виде
С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.
Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот. Я для этого использовал стек.
В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.
Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:
Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-‘, а затем инвертируем число и преобразуем как беззнаковое.
Результат работы программы выглядит вот так:
Как вывести на консоль в десятичном виде очень большое число (> 32 бит) читайте здесь.
Вывод чисел в восьмеричном виде
Выводить числа в восьмеричном виде приходится достаточно редко, поэтому подробно описывать не буду. Можно либо делить число последовательно на 8, либо преобразовывать в цифры группы по 3 бита. Я использовал второй вариант.
Результат работы программы:
Вывод чисел в других системах счисления
Реализуется также, как вывод в десятичном виде — с помощью алгоритма последовательного деления на основание системы счисления. Например, если вам нужно вывести число в пятеричной системе счисления, делить надо на 5, а не на 10.
Упражнение
Напишите программу для вывода на консоль массива слов со знаком в десятичном виде (например, через запятую). Для вывода чисел можете воспользоваться моим примером или написать свою собственную процедуру. Результаты можете писать в комментариях или на форуме.
Исходные коды программ целиком:
1. Вывод в двоичном виде:
Источник