9. Косвенная адресация. Команда организации цикла. 9.1. Индексная адресация

Лекции по предмету «Информатика»
Информация о работе
  • Тема: 9. Косвенная адресация. Команда организации цикла. 9.1. Индексная адресация
  • Количество скачиваний: 3
  • Тип: Лекции
  • Предмет: Информатика
  • Количество страниц: 11
  • Язык работы: Русский язык
  • Дата загрузки: 2014-05-05 15:33:01
  • Размер файла: 30.88 кб
Помогла работа? Поделись ссылкой
Информация о документе

Документ предоставляется как есть, мы не несем ответственности, за правильность представленной в нём информации. Используя информацию для подготовки своей работы необходимо помнить, что текст работы может быть устаревшим, работа может не пройти проверку на заимствования.

Если Вы являетесь автором текста представленного на данной странице и не хотите чтобы он был размешён на нашем сайте напишите об этом перейдя по ссылке: «Правообладателям»

Можно ли скачать документ с работой

Да, скачать документ можно бесплатно, без регистрации перейдя по ссылке:

9. Косвенная адресация. Команда организации цикла.

9.1. Индексная адресация

До сих пор мы писали программы, в которых обращались к отдельным ячейкам памяти, т.е. в терминологии языков программирования высокого уровня работали с простыми переменными. Но очень полезно иметь возможность работать с данными, объединенными в некоторые совокупности.
Массив на уровне реализации — это множество ячеек одинакового размера, занимающих непрерывную область памяти. Адресом массива называется адрес первого элемента массива. На рис. 9.1. схематически показан массив из четырех слов. Адрес массива ds:408

ds:0408 ds:040A ds:040C ds:040E

Рис. 9.1.
Посмотрим, как в процессоре реализовано обращение к элементам массива.
Допустим, мы рассматриваем байтовый массив по адресу (смещению) 300. Первый элемент (с номером нуль) расположен по адресу 300, второй элемент — по адресу 301 и т.д. С теми возможностями, которыми мы сейчас располагаем, мы можем записать в аккумулятор, допустим, четвертый элемент командой mov al,[303] Но, оказывается, то же самое можно сделать иначе:
mov si,3
mov al,[si+300]
В индексный регистр SI мы поместили номер элемента, а затем выбрали его по адресу 300+SI. Можно было записать эту команду иначе: mov al,300[si] с тем же эффектом. Вместо SI здесь можно было использовать DI и BX (и даже BP — для com-программ, которые мы создаем в отладчике — для них совпадают значения сегментных регистров, а вообще-то BP предназначено для стекового сегмента).
Значение SI можно вычислять в программе, и тем самым обращаться к разным элементам массива.
Пример. В массиве из 16 байт, расположенном с 200-го адреса, сосчитать количество элементов, величина которых больше значения –2.
mov cx,10h; Количество элементов — в CX
mov bx,0 ; Счетчик подходящих элементов — в BX
mov si,0 ; SI — индекс массива
m: cmp byte ptr [si+200],-2 ; Очередной элемент больше –2 ?
jng n ; Нет — на n
inc bx ; Да — увеличить счетчик
n: inc si ; Перейти к следующему элементу
dec cx ; Уменьшить счетчик повторений цикла
jnz m ; Если счетчик повторений отличен от нуля — на m
nop ; В BX — количество подходящих элементов

Использованный метод адресации носит название — индексный.

9.2. Команды организации циклов
В только что разобранной программе легко выделить конструкцию для организации цикла с фиксированным числом повторений:
для k = 1 до N шаг 1 выполнить <тело цикла>
Ее эквивалент на языке Ассемблера
mov cx,N
next:
<тело цикла>
dec cx
jnz next
Последние две команды можно заменить одной командой loop next.
Управление циклом loop opr CX ← CX–1.
Если CX 0, то IP ←IP + Data8

LOOP — петля Флаги не изменяются
Обратите внимание, что счетчик повторений — обязательно регистр CX. Команда loop проверяет именно его содержимое.
Если цикл включен в программу, и количество повторений тела цикла предварительно вычисляется, то имеет смысл проверить, что CX отличен от нуля.
Задача. Пусть в программе, приведенной выше команда mov cx,10h заменена на mov cx,0. Сколько раз будет выполнено тело цикла?
Ответ: 65536.
Проверку на равенство содержимого CX нулю легко осуществить последовательностью двух команд:
cmp cx,0
je error
но можно для этой же цели воспользоваться одной командой jcxz.
Перейти по CX = 0 jcxz opr если CX = 0 , то IP IP + Data8

Jump if CX equal Zero Флаги не изменяются
Команду ставят перед началом цикла. Метка opr, разумеется, находится вне цикла. Эту команду причисляют к командам условного перехода, хотя она заведомо выпадает из их ряда: jcxz проверяет содержимое регистра CX, а прочие команды — состояние флагов.
Имеются еще две команды организации циклов: LOOPZ/LOOPE opr и LOOPNZ/LOOPNE opr (через дробную черту указаны альтернативные мнемоники). В них непосредственно перед уменьшением CX проводится проверка флага ZF и в зависимости от его значения выполнение цикла продолжается до исчерпания CX или досрочно прекращается. Но эти команды редко используются. Поэтому их изложение опустим.

9.3. Косвенная адресация
Настало время перечислить все методы адресации. Нами уже изучены методы:
• регистровый: inc bx
• непосредственный: mov ax,6
• прямой: dec word ptr [200].
При использовании косвенной адресации используется один или два регистра, в которых хранятся некоторые слагаемые адреса. Дадим сразу общую схему:

Здесь EA — Effective Address — эффективный (исполнительный) адрес. Фигурные скобки означают, что вычисляется один из перечисленных внутри них элементов (в том числе и никакой: на это указывает пробел). Возможны любые комбинации перечисленных элементов, за исключением двух: 1) [] , т.е. недопустимы квадратные скобки без содержимого, 2) [BP]. Если все-таки нужно использовать [BP], то для этого следует использовать адресацию [BP+D8], где D8 = 0 (в программе на языке Ассемблера можно записать обращение к операнду, как [BP], но Ассемблер автоматически переведет его в форму [BP+0]). Причину, почему использование [BP] невозможно, мы узнаем позже, когда изучим кодирование команд.
Итак, смысл приведенной выше формулы ясен: процессор складывает содержимое базового регистра, индексного регистра и смещение (displacement). Полученная величина является адресом, а точнее, смещением (offset).
В каком же сегменте вычисляется это смещение? Здесь действует правило: если в выражении встречается BP, то полный адрес вычисляется с использованием содержимого сегмента стека SS, во всех остальных случаях используется сегмент данных DS.
Отдельные комбинации регистров, входящих в EA, имеют свои названия. В литературе по поводу названий методов адресации нет единого мнения. Чаще употребляются следующие наименования.
1) [BX], [SI], [DI] (не [BP]!) — регистровая косвенная адресация.
2) [BX+20], 20[BX], [BP+8] — базовая адресация.
3) [SI–4], –4[SI], [DI+6] — индексная адресация.
4) [BX+SI+2], [BX][SI+2], 2[BX][SI] — базовая индексная адресация со смещением. Все три выражения эквивалентны. Предпочтительнее использовать первое из них.
Остается неясным, чем базовый метод адресации отличается от индексного. В 16-разрядном режиме — ничем! Эти названия, по-видимому, были даны "на вырост", с учетом перспективы. Когда мы приступим к изучению 32-разрядного режима, то увидим, чем отличаются эти два режима.

9.4. Пример программы обработки массива (задание A4)
9.4.1. Формулировка задания
Дан массив A из 16 байтов. Скопировать его в массив B, заменяя элементы, равные 2, на нуль. Поместить в массив C адреса (смещения) измененных элементов из массива A. Сосчитать количество элементов, которые не подверглись изменению.
Отчет должен содержать: текст программы с комментариями, входные и выходные значения массивов.

9.4.2. Программа
Разместим массив A, начиная с адреса 200, массив B — с адреса 210 (адреса 16-ричные!), массив C — с адреса 220.
Приведем текст программы.
mov si,200 ; Адрес массива A — в SI
mov di,210 ; Адрес массива B — в DI
mov bx,220 ; Адрес массива C — в BX
xor dx,dx ; Счетчик неизменяемых элементов — в DX
mov cx,10 ; Количество элементов (10h = 16)
; в обрабатываемом массиве — в CX
n: mov al,[si]; Поместим очередной элемент массива A в AL
cmp al,2 ; Сравним его с 2
jne m ; Если элемент равен 2,
mov byte ptr [di],0 ; то записать в массив B нуль,
mov [bx],si ; поместить в C смещение измененного элемента,
inc bx ; и переместить в массиве C указатель
inc bx ; на следующий элемент,
jmp c
m: mov [di],al ; иначе — записать в B элемент из A,
inc dx ; увеличить счетчик неизменяемых элементов,
c: inc si ; переместить указатели в массиве A
inc di ; и в массиве B
loop n ; Конец цикла
nop

Тестовые данные выберем следующие:
до выполнения программы:
массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
массив B: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
массив C: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
после выполнения программы:
массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 (без изменений)
массив B: 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
массив C: 200, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
регистр DX = 14 = 0Eh.
Текст программы и данные составляют содержание письменного отчета.

9.4.3. Выполнение задания в отладчике Turbo Debugger.
1) Запуск Turbo Debugger. Ввод программы.
C:prog>td.exe
На экране появится окно CPU. Сейчас окно CPU занимает часть экрана. Нажмем F5 (Zoom), и окно будет "распахнуто" до размеров экрана.
Перейдем к выполнению задания. Находясь в панели кода, начнем ввод команд программы. Нажмем букву m (первая буква команды mov si,200). Появляется окно "Enter instruction to assemble" (введите команду, которую нужно ассемблировать). Завершаем ввод команды и нажимаем Enter. Встроенный мини-ассемблер немедленно преобразует ее в машинный код. Для ввода следующей команды вызываем команду ассемблирования из локального меню нажатием Ctrl+A (или Alt+F10→Assemble...). В появляющемся диалоговом окне мы видим ранее введенную команду mov si,0200. Нажимем клавишу ↓ (ранее введенная команда становится выделенной). Нажимаем клавишу End (выделение снимается). Редактируем команду, чтобы получить mov di,0210. Аналогично набираем остальные команды.

cs:0100►BE0002 mov si,0200
cs:0103 BF1002 mov di,0210
cs:0106 BB2002 mov bx,0220
cs:0109 33D2 xor dx,dx
cs:010B B91000 mov cx,0010
cs:010E 8A04 mov al,[si]
cs:0110 3C02 cmp al,02
cs:0112 7509 jne 011D
cs:0114 C60500 mov byte ptr [di],00
cs:0117 8937 mov [bx],si
cs:0119 43 inc bx
cs:011A 43 inc bx
cs:011B EB03 jmp 0120
cs:011D 8805 mov [di],al
cs:011F 42 inc dx
cs:0120 46 inc si
cs:0121 47 inc di
cs:0122 E2EA loop 010E
cs:0124 90 nop
Команды перехода (адреса 010D и 011B) приходится набирать дважды: сначала как jne 100 и jmp 100 (так как нам неизвестно числовое значение меток m и c), и только после ввода всего текста, когда мы выясним, что m = 011D, а с = 0120, мы можем исправить эти команды, поставив там нужные числовые значения меток, т.е. адреса перехода.
2) Сохраним код программы. Для этого, нажимая Shift+Tab, перейдем в панель данных. Вызовем локальное меню: Alt+F10. Выбираем команду Block. Появляется новое меню (на это указывает стрелка, замыкающая название команды Block). Меню показано на рис. 9.2.

Clear...
Move...
Set...
Read...
Write...
Рис. 9.2.
Выбираем команду Write.... Появляется окно с запросом имени файла: "Enter write file name". В поле ввода File Name введем имя файла a4v0.com. Имя, разумеется, может быть любым, а вот расширение — только .com, не .exe. Нажимаем Enter.
Появляется новое окно с запросом адреса блока памяти и количества байтов в блоке: Enter memory address, count. Наша программа расположена, начиная с адреса CS:0100. В панели данных подразумевается, что сегментная часть адреса блока находится в регистре DS. Но в панели регистров мы видим, что содержимое сегментных регистров CS и DS совпадает. Поэтому адрес блока можно задать как 100 — только смещение (offset), без указания сегментной части адреса. Количество записываемых байтов: 0124h – 00FFh = 25h. Итак, в поле ввода указываем через запятую два числа: 100, 25, и нажимаем Enter.
Заметим, что если бы размер программы составил бы 2Dh, то ввод 100, 2d был бы ошибкой: в файл было бы записано всего два байта, т.к. d — суффикс десятичного числа. В этом случае обязательно надо указывать суффикс шестнадцатеричного числа: 100, 2dh.
3) Введем исходные данные. Сейчас активна панель данных.
Перейдем на адрес 200: Alt+F10→Goto. Появится окно с запросом адреса, на который надо перейти: Enter address to position to. Набираем 200 и нажимаем Enter. В панели данных отображается содержимое байтов, начиная с адреса DS:0200.
Сначала обнулим массивы A,B,C: Alt+F10→Block→Clear. Появляется уже знакомый запрос: Enter memory address, count. Вводим 200,40 и нажимаем Enter.
Теперь введем единицы в массив A: Alt+F10→Block→Set. В ответ на запрос Enter address, count, byte value вводим 200,10,1 и нажимаем Enter. Убеждаемся, что 16 байтов, начиная с адреса 200, содержат единицы.
Подправим первые три элемента массива A: Alt+F10→Change. В ответ на запрос Enter new data bytes вводим 2,1,2 и нажимаем Enter.
Исходные данные подготовлены. Может быть, имеет смысл сохранить файл a4v0.com еще раз, разместив в нем исходные данные. Для этого надо повторить действия предыдущего пункта, только размер программы будет иной: 240h –0FFh = 141h. При этом в файле сохранится и "мусор" из диапазона адресов 125h –1FFh.
4) Разместим на экране окна для отображения содержимого массивов A и B. Массив C будем отображать в панели данных окна CPU.
Массив A. F10→View→Dump. На экране появляется окно с содержимым, аналогичным панели данных. Alt+F10→Goto. Вводим 200.
Переместим окно в левый нижний угол: Ctrl+F5 (окно приобретает тонкую зеленую рамку) и клавишами перемещения курсора перемещаем его. Затем нажимаем Shift и клавиши перемещения курсора — окно меняет размеры. Добиваемся, чтобы в окне отображалось ровно 16 байтов — это две строки. Нажатием Enter положение и размеры окна закрепляются, зеленая рамка заменяется обычной.
Операцию изменения положения и размеров окна проще осуществлять мышью. Для изменения положения окна ухватите его мышью за верхнюю рамку и перемещайте. Для изменения размера окна ухватите его мышью за правый нижний угол рамки.
Массив B. F10→View→Another→Dump. Появляется окно. Переходим к адресу ds:210: Alt+F10→Goto, вводим 210. Переместим окно в правый нижний угол и изменим его размеры, так чтобы отображалось 16 байтов.
Массив C. Перейдем в окно CPU. Это можно сделать двумя способами: либо последовательно нажимая F6 (переход из окна в окно), либо нажимая Alt+номер_окна, в нашем случае Alt+1. В панели данных перейдем на адрес 220. Массив C — это массив слов. Отобразим содержимое панели как слова: Alt+F10→Display as→Word. Изменим размер окна CPU так, чтобы на экране были размещены все три окна по следующей схеме (рис. 9.3):
код регистры флаги

данные — массив C стек
данные — массив A данные — массив B
Рис. 9.3.
5) Программу на языке Ассемблера никогда не следует сразу запускать на выполнение. Ее нужно обязательно прогонять по шагам, так как ошибки в таких программах сделать легче, чем в программах на языке высокого уровня, а последствия этих ошибок — куда тяжелее.
Мы находимся в окне CPU, в панели данных. Чтобы перейти в панель кода, нажмем Tab. В счетчике команд IP находится адрес 100, что соответствует указателю на первую команду нашей программы в панели кода. При последовательном нажатии на F7 указатель будет перемещаться по программе. Внимательно прослеживаем содержимое регистров и ячеек памяти. Если должна выполняться команда работы с памятью, то справа вверху в панели кода отображается текущее содержимое ячейки памяти. Это полезно иметь в виду, чтобы быть уверенным, что из памяти действительно выбирается нужная информация (т.е. корректно перемещаются указатели, правильно используются методы адресации и т.д.).
Если в программе замечена ошибка, исправляем ее. Для этого возможно придется вставить новые команды или удалить существующие. Чтобы заново не набирать команды, расположенные вслед за исправленными командами (если они занимают больше места в памяти), воспользуйтесь в панели данных командой перемещения данных (в том числе и кода!) в памяти: Alt+F10→Block→Move. Разберитесь самостоятельно, как работать с этой командой.
Если нужно вновь прогнать программу по шагам, сначала следует в счетчик команд занести 0100 — адрес первой команды. Для этого переместитесь в панели кода на команду с адресом 100: Alt+F10→Goto и введите 100. Будет выделена команда с адресом 100. А теперь сделаем так, чтобы IP содержал адрес этой команды: Alt+F10→New cs:ip. В строке подсказки при выборе этого пункта меню вы увидите: Set the cs:ip to the current location — установить CS:IP в текущее положение курсора. Эквивалентная команда — Ctrl+N. Теперь вновь можно нажимать F7.
Выполнить программу как единое целое можно так: выделим команду nop в конце программы. При этом в IP, разумеется, должен быть стартовый адрес программы. Нажмем клавишу F4 (Here — здесь). Программа будет выполнена.

9.5. Еще примеры.
Для усвоения приемов работы с отладчиком разберите еще два примера. Обязательно выполните их на компьютере.
Пример. В массивах A и B (каждый из 8 слов) расположены «сверхдлинные» целые числа. Выполнить над ними сложение: A += B. Адреса (смещения) тех элементов массива A, при вычислении которых произошел перенос единицы в следующее слово, записать в массив C. Вычислить их количество.

Для эффективного решения этой задачи познакомимся с командами изменения флага CF.
Перечислим команды для изменения флага CF.
Сбросить флаг переноса clc CF  0
CLear CF CF = 0

Инвертировать флаг переноса cmc

CoMplement CF CF = 0

Установить флаг переноса stc CF  1
SeT CF CF = 1


Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h

Тест
До
A FFFF, 2, FFFE, FFFD, FFFС, FFFС, FFFС, FFFС
B 1, 3, 2, 2, 2, 2, 2, 2 (число: 00020002000200020002000200030001)
C 0, 0, 0, 0, 0, 0, 0, 0

После
A 0, 6, 0, 0, FFFF, FFFE, FFFE, FFFE
B 1, 3, 2, 2, 2, 2, 2, 2
C 200, 204, 206, 0, 0, 0, 0, 0

mov si,200 ; Адрес массива A — в SI
mov di,210 ; Адрес массива B — в DI
mov bx,220 ; Адрес массива C — в BX
xor dx,dx ; Счетчик элементов — в DX
mov cx,8 ; Количество элементов в массиве A — в CX
clc ; Обнулить флаг CF,
; чтобы отдельно не обрабатывать младший элемент
n: mov ax,[si] ; Выполнить сложение очередных элементов
adc ax,[di]
mov [si],ax
jnc m ; Если возник единичный бит переноса,
inc dx ; то увеличить счетчик
mov [bx],si ; и записать адрес в массив C
inc bx ; Переместить указатель в массиве C
inc bx
m: inc si ; Переместить указатель в массиве A
inc si
inc di ; Переместить указатель в массиве B
inc di
loop n
nop

Код в отладчике

cs:0100 BE0002 mov si,0200
cs:0103 BF1002 mov di,0210
cs:0106 BB2002 mov bx,0220
cs:0109 33D2 xor dx,dx
cs:010B B90800 mov cx,0008
cs:010E F8 clc
cs:010F 8B04 mov ax,[si]
cs:0111 1305 adc ax,[di]
cs:0113 8904 mov [si],ax
cs:0115 7305 jnb 011C
cs:0117 42 inc dx
cs:0118 8937 mov [bx],si
cs:011A 43 inc bx
cs:011B 43 inc bx
cs:011C 46 inc si
cs:011D 46 inc si
cs:011E 47 inc di
cs:011F 47 inc di
cs:0120 E2ED loop 010F
cs:0122 90 nop

Дамп памяти после выполнения программы
ds:0200 0000 0006 0000 0000
ds:0208 FFFF FFFE FFFE FFFE
ds:0210 0001 0003 0002 0002
ds:0218 0002 0002 0002 0002
ds:0220 0200 0204 0206 0000

Пример. Дан массив A из 16 байтов. Из байтов, равноотстоящих от середины массива, образовать слово. При этом байт с меньшим адресом становится младшим. Начинать с крайних элементов, продвигаясь к середине. Если слово не делится на 3 (числа знаковые), то записать его в массив слов B, а адрес (смещение) младшего байта в массив С. Сосчитать количество таких элементов.

Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h

Тест
Сначала составим пары чисел.
0 и 4 → 04, 04 mod 3 = 1 → не делится;
ff и fd → fffd = –3, –3 mod 3 = 0 → делится;
В калькуляторе TD вычисляем, что 2567h mod 3 дает результат 2 → не делится.
В калькуляторе TD набираем десятичное число 1452d (его сумма цифр делится на 3). А его 16-ричный эквивалент: 5AC;
Для оставшихся элементов выбираем 0 и 1 → 01 → не делится.

Итак, данные для тестирования
До
A 4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0
B 0, 0, 0, 0, 0, 0, 0, 0
C 0, 0, 0, 0, 0, 0, 0, 0

После
A 4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0
B 4, 2567, 1, 1, 1, 1
C 200, 202, 204, 205, 206, 207, 0, 0
количество: 6

Идея алгоритма: настроим указатели на первый и последний элемент массива, и будем продвигаться к середине.

mov si,200 ; Адрес первого элемента массива A - в SI
mov di,20F ; Адрес первого элемента массива A - в DI
mov bx,210 ; Адрес массива B в BX
xor bp,bp ; Счетчик подходящих элементов – в BP
mov cx,8 ; Количество пар элементов – в CX
n: mov al,[si]; Младший элемент — в AL
mov ah,[di]; Старший элемент — в AH
cwd ; Знаковое делимое в DX:AX
push bp
push ax
mov bp,3 ; Делитель в BP
idiv bp
or dx,dx ; Если число не делится на 3,
je m
pop ax ; то восстановить AX
pop bp ; и счетчик элементов
mov [bx],ax; записать AX в массив B,
mov [bx+10],si ; а адрес младшего элемента в массив C
inc bp ; Увеличить счетчик
inc bx ; Переместить указатель в B
inc bx
m: inc si ; Переместить указатели в массиве A
dec di
loop n
nop

Здесь может возникнуть вопрос: почему для проверки делимости числа, размещенного в AX, мы не воспользовались более экономным фрагментом
mov dh,3
idiv dh
or ah,ah
je m
Напомним ответ: здесь возможно переполнение при делении (более подробно это обсуждалось в гл. 8).
В этой программе нам не хватило регистров для размещения промежуточных результатов, поэтому пришлось воспользоваться стеком для их временного хранения. Вы заметили недостаток программы? Мы увеличиваем стек при обработке каждого элемента, а уменьшаем его только при обработке элемента, который не делится на 3. Исправить программу можно, например, так: после команды pop bp вставить команду sub sp,4, а после метки m команду add sp,4. После первой из этих команд указатель стека будет принудительно перемещен вниз на два слова, а после второй — вернется на прежнее место.