18. Битовые операции
18.1. Булевские команды
Эти команды оперируют с отдельными битами ячейки, независимо от других битов ячейки.
Инвертировать not opr
"ИЛИ" (дизъюнкция) or dst,src
"И" (конъюнкция) and dst,src
"Исключающее ИЛИ" xor dst,src
Логическое сравнение test opr1,opr2 opr1 opr2
Команда not на флаги не действует, остальные команды сбрасывают CF и OF, а флаги SF, ZF, PF изменяют по обычным правилам. Рассмотрим серию примеров, иллюстрирующих работу этих команд.
Пример. Команда инвертирования битов. Операция НЕ применяется к каждому биту.
mov al, 10011101b ; AL = 9Dh
not al ; AL = 01100010b = 62h
Остальные команды двухоперандные. Обычно они используются по следующей схеме. В источник src помещается так называемая "маска" — непосредственный операнд. Маска указывает, как изменять биты приемника.
Пример. Установка и сброс битов в соответствии с маской.
1) В регистре DL установить 6-й, 3-й и 1-й биты. Применяем дизъюнкцию с маской, используя правила поглощения
маска 0 1 0 0 1 0 1 0
DL b7 b6 b5 b4 b3 b2 b1 b0
DL маска
b7 1 b5 b4 1 b2 1 b0
Команда имеет вид or dl, 01001010b (или or dl, 4Ah).
2) В регистре CH сбросить 4-й и 3-й биты. Применяем конъюнкцию с маской, используя правила поглощения
маска 1 1 1 0 0 1 1 1
CH b7 b6 b5 b4 b3 b2 b1 b0
CH маска
b7 b6 b5 0 0 b2 b1 b0
Команда and ch,11100111b (или and ch,0E7h).
3) Инвертировать 4-й и 3-й биты регистра BH. Применяем "исключающее ИЛИ", используя правила
маска 0 0 0 1 1 0 0 0
CH b7 b6 b5 b4 b3 b2 b1 b0
CH маска
b7 b6 b5 b4
b3
b2 b1 b0
Команда xor ch,11000b (или xor ch,18h).
Команда xor применяется для обнуления регистров: код xor ax,ax короче, чем mov ax, 0 (проверьте!).
Пример [The Assembly Gems Pages. http://www.df.lth.se/~john_e/]. Вычисление абсолютной величины для содержимого AX без ветвлений.
cwd
xor ax,dx
sub ax,dx
Самостоятельно разберите этот фрагмент.
Вместо команды
cmp ax,0 ; AX = 0 ?
предпочитают использовать более короткие команды and ax,ax или or ax,ax. Они оставляют содержимое AX неизменным, но устанавливают или сбрасывают флаг ZF.
Команду test используют, чтобы проверить, установлен ли определенный бит операнда, и при этом не "испортить" операнд.
Пример. Установлен ли 5-й бит DL? Если ДА, перейти на метку m.
test dl,00100000b
jnz m
С помощью этой команды легко проверить делимость числа на степени двойки. Например, если два младших бита числа — нули, то оно делится на 4:
test cx, 11b
jz yes
18.2. Команды сдвигов
Эти команды перемещают содержимое ячейки влево или вправо. Одним из операндов этих команд является количество сдвигов cnt. Оно либо равно 1, либо определяется содержимым регистра CL (при этом CL сохраняет свое содержимое после операции). Начиная с 286 процессора количество сдвигов, отличное от единицы, можно указывать как непосредственный операнд.
Естественно, возникает вопрос (для определенности говорим о сдвиге на одну позицию вправо): куда девается младший бит (точнее, его содержимое)? что записывается в старший бит? В командах сдвига эти вопросы решаются по-разному.
18.2.1. Логические и арифметические сдвиги.
Логический/арифметический
сдвиг влево shl/sal opr,cnt
(Shift Logical/Arithmetic Left)
CF opr
0
Логический сдвиг вправо shr opr,cnt
(Shift Logical Right)
opr CF
0
Арифметический сдвиг вправо sar opr,cnt
(Shift Arithmetic Right)
opr CF
Флаг CF устанавливается в соответствии с рисунками. Флаг OF имеет смысл , если cnt = 1.
Сначала остановимся на формальном синтаксисе команд.
Неправильно Правильно
1) sar ax,2 (в 286 и выше — правильно!) 1) mov cl,2
sar ax,cl
2) mov bl,3
shr dx,bl (количество сдвигов только в CL) 2) shl ax,1
Арифметические сдвиги предназначены для выполнения умножения и деления на степени двойки (табл.6.1).
Таблица 6.1
операнд умножение деление
знаковый sal/shl sar
беззнаковый shr
Умножение на 2 — сдвиг влево и приписывание справа нуля. Докажите, что OF = CF SF.
Деление на 2 — сдвиг вправо, в CF попадает остаток от деления. При арифметическом сдвиге вправо дублируется знаковый бит: поэтому отрицательное число остается отрицательным.
Пример. Пусть AL = –120 = 10001000. sar al,1 дает AL = –60 = 11000100. Проверьте.
К сожалению, результат деления с помощью команды sar не всегда совпадает с результатом деления, полученным с помощью команды idiv. Сравните результат от деления –5 на 2, полученный с помощью и той и другой команды.
18.2.2. Циклические сдвиги.
Циклический сдвиг влево rol opr,cnt
(ROtate Left)
CF opr
Циклический сдвиг вправо ror opr,cnt
(ROtate Right)
opr CF
Циклический сдвиг влево через перенос rcl opr,cnt
(Rotate through Carry Left)
CF opr
Циклический сдвиг вправо через перенос rcr opr,cnt
(Rotate through Carry Right)
opr CF
Пример. Двойное слово в DX:AX умножить на 4.
shl ax,1 ; Старший бит AX — в CF
rcl dx,1 ; CF — в младший бит DX
shl ax,1
rcl dx,1
Пример. Сосчитать количество единичных битов в AX. Результат поместить в BX.
xor bx,bx ; обнулить счетчик единичных битов
mov cx,16 ; разрядность слова — в CX
n: rol ax,1 ; циклический сдвиг влево, старший бит — в CF
adc bx,0 ; добавить величину CF к счетчику
loop n
После выполнения этого фрагмента в AX будет восстановлено первоначальное содержимое.
Пример. Вращать содержимое AX влево, пока в старшем (15-ом) бите не появится 1, и перейти на метку n1. Если AX = 0, то перейти на метку n0.
Воспользуемся командой rcl (можно и rol) и тем, что OF = CF SF.
cmp ax,0
je n0 ; если AX = 0 — на n0
js n1 ; если старший бит установлен — на n1
mov cx,16 ; разрядность AX — в CX
m: rcl ax,1
jo n1 ; если CF = 0 и SF = 1 — на n1
loop m
(метки n0 и n1 не показаны)
18.3. Средства Ассемблера для битовых операций
На этапе ассемблирования над числами возможны следующие логические операции: NOT, AND, OR, XOR, SHL, SHR. Приведем примеры.
L = 101b
L = L SHL 2 ; L = 10100b
mov al, NOT 3 ; AL = 0FCh
mov bh, 1100b OR 1010b ; BH = 1110b
Когда требуется плотная упаковка нескольких числовых значений, диапазон изменения которых невелик, их помещают в заранее определенные битовые поля ячейки памяти. Рассмотрим пример, взятый из [5].
Разместим в слове информацию о работнике (табл.6.2).
Таблица 6.2
Биты Длина
поля Имя
поля Назначение
15 1 sex пол (0 — мужчина, 1 — женщина)
14 1 married семейное положение (0 — холост, 1 — женат)
13:10 4 children количество детей (0 — 15)
9 1 xxx зарезервировано
8:2 7 age возраст (0 — 127)
1:0 2 school уровень образования (0 — 3)
Пусть в программе имеется описание:
Johnson DW 0100100010001011b
Нужно получить количество детей Джонсона (чтобы предоставить ему налоговые льготы). Сначала дадим "лобовое" решение задачи.
mov ax, Johnson ; поместить данные в AX
and ax, 0011110000000000b ; выделить поле children
shr ax, 10 ; прижать поле к правому краю
; (в AX — количество детей)
Стоит нам ошибиться хотя бы на один бит в маске и количестве сдвигов и мы получим неверный результат. Нельзя ли поручить этот скрупулезный подсчет битов Ассемблеру? Да, TASM предоставляет нам такую возможность. Приведем полный текст программы
.MODEL small
.286
.STACK 100h
.DATA
person RECORD sex:1=0,married:1,children:4,xxx:1,
age:7,school:2=1
Johnson person <,1,2,,34,3>
.CODE
start:
mov ax,@data
mov ds,ax
mov ax, Johnson
and ax, MASK children
shr ax, children
mov ax,4c00h
int 21h
END start
Прокомментируем текст программы. Директива .286 разрешает использовать инструкции 286-го процессора. Эта директива нужна, чтобы TASM сгенерировал код инструкции shr ax,10 (посмотрите в Turbo Debugger, что получится, если не указать эту директиву). Директива RECORD задает шаблон описания битовых полей. Этот шаблон носит имя person. В шаблоне указаны имя битового поля и его ширина, а не биты, которое занимает поле, — из-за этого пришлось включить в шаблон зарезервированное поле xxx. В двух полях (sex и age) после знака равенства дано значение "по умолчанию", которое может быть переопределено при описании с использованием шаблона. В шаблоне необязательно описывать все биты ячейки: можно было описать, например, только поля age и school. Само описание шаблона памяти не выделяет, зато описание выделяет в памяти слово в соответствии с шаблоном. Использован символ для продолжения директивы на следующей строке. В этом описании заданы значения всех полей, кроме xxx (значение которого безразлично), и sex (которое берется по умолчанию из шаблона).
Три строки в программе после загрузки DS полностью эквивалентны трем строкам, приведенным ранее. Операция (выполняемая на этапе ассемблирования) MASK поле_записи возвращает маску, необходимую для выделения поля записи. Значение поля_записи — число битов, на которые нужно осуществить сдвиг вправо, чтобы прижать поле к правому краю ячейки.
Если нужно прижать поле к левому краю слова, можно воспользоваться командой
shl ax, 16 – WIDTH children – children.
Здесь в вычислениях участвует ширина поля WIDTH поле.
18.4. Задание D2. Пример программы, включающей битовые операции
18.4.1. Формулировка задания
Общее задание. Написать подпрограмму, обрабатывающую массив, и макрос для ее вызова. Подпрограмма обработки массива вызывает подпрограмму обработки элемента массива. В программе привести примеры вызова макроса. Программу, макрос и подпрограммы разместить в отдельных файлах.
В Turbo Debugger записать в файл протокола окна данных до и после выполнения программы. Записать туда же состояние стека в момент выполнения подпрограммы обработки элемента и интерпретировать его.
Отчет должен содержать:
1) распечатки файлов с программами и макросом,
2) распечатка окон с данными до и после выполнения программы,
3) окно стека с комментариями,
4) адрес подпрограммы обработки массива:
из листингов программ,
из карты памяти,
из отладчика.
5) размер кода и данных программы.
Задание. Дан массив слов (байтов) указанного размера. Над каждым элементом массива выполнить операцию: если битовое поле 6:4 совпадает с битовым полем 2:0 и с заданным образцом, то установить старший бит элемента, иначе — сбросить его.
18.4.2. Тексты программ
При выполнении задания вам придется как бы раздвоиться: на разработчика программы и ее пользователя. Разработчик пишет подпрограмму обработки массива и макрос, который дает возможность ее удобного использования. Пользователь пишет главную программу и испытывает подпрограмму на различных входных данных.
По условию задачи следует разместить программу и подпрограммы в разных файлах. Сначала напишем подпрограммы. Разместим их в файле sub2v0.asm. Сконструируем шаблон (рис.18.1) для слова и байта одновременно (можно, конечно, использовать и несколько шаблонов).
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
b15 d2 b7 f6_4 d1 f2_0
Рис.18.1.
Фактически для нас представляют интерес только интерес только поля f6_4 и f2_0, из которых берутся исходные данные, и поля b15 и b7, которые (возможно) претерпевают изменения. Но приходится вводить названия и для неиспользуемых полей d1 и d2, в соответствии с синтаксисом описания RECORD.
sub2v0.asm
JUMPS ; Оптимизация переходов
.286 ; Разрешены инструкции 286-го процессора
TypeByte EQU 1
TypeWord EQU 2
GLOBAL treat_array:PROC
.MODEL small
.CODE
;--------------------------------------------------------------------
; Подпрограмма обработки элемента массива
; Вход: AX(AL) — элемент массива
; BP — образец
; DX — тип элементов (1 - байты, 2 - слова)
; Выход: AX(AL) - измененный элемент массива
;--------------------------------------------------------------------
treat_elem PROC
template RECORD b15:1, d2:7, b7:1, f6_4:3,
d1:1, f2_0:3
push ax bx ; Сохранить рабочие регистры
mov bx,ax ; Дублировать элемент в bx
and ax,MASK f6_4 ; Выделить битовое поле 6:4
shr ax,f6_4 ; Прижать его к правому краю
and bx,MASK f2_0 ; Выделить битовое поле 2:0
cmp ax,bx ; Сравнить битовые поля
jne clear ; Если не совпадают, сбросить старший бит
cmp ax,bp ; Сравнить битовое поле с образцом
jne clear ; Если не совпадают, сбросить старший бит
; Установка старшего бита
pop bx ax ; Восстановить регистры
cmp dx,TypeWord ; Если тип операнда слово –
je set_b15 ; на set_b15
or al, MASK b7 ; Иначе — установить старший бит байта
jmp ret_elem ; На выход
set_b15:
or ax, MASK b15 ; Установить старший бит слова
jmp ret_elem
; Сброс старшего бита
clear: pop bx ax
cmp dx, TypeWord
je clear_b15
and al, NOT MASK b7 ; Сбросить старший бит байта
jmp ret_elem
clear_b15:
and ax, NOT MASK b15 ; Сбросить старший бит слова
ret_elem:
ret
treat_elem ENDP
;-------------------------------------------------------------------
; Подпрограмма обработки массива
; Вход: SI — адрес массива
; CX — количество элементов
; BP — образец
; DX — тип элементов (1 - байты, 2 - слова)
; Выход: CF = 1, если входной массив пуст
; CF = 0, если входной массив непуст
; Результат: измененный массив
;-------------------------------------------------------------------
treat_array PROC
jcxz error ; Если массив нулевой длины — на error
m: mov ax,[si] ; Загрузить очередной элемент в AX (в AL)
call treat_elem ; Обработать элемент
mov [si],ax ; Вернуть элемент в массив
add si,dx ; Переместить указатель на следующий элемент
loop m
clc ; Нормальное завершение
ret
error: stc; Сообщение об ошибке, если массив пустой
ret
treat_array ENDP
END
Обратим внимание на следующие моменты.
1) Мы сообщаем компилятору директивой
GLOBAL treat_array:PROC
что treat_array — это глобальное имя, т.е. оно будет использоваться программами, расположенными в других файлах. Спецификатор PROC означает, что это имя некоторой процедуры. Компилятор разместит это имя в таблице глобальных имен. Ей впоследствии воспользуется компоновщик.
2) Директива END не содержит метки стартового адреса, т.к. в файле нет главной программы — только подпрограммы.
3) Вместо того чтобы записывать команды
push ax
push bx
мы записываем эти команды одной строкой. Такую возможность предоставляет Turbo Assembler.
4) В начале каждой процедуры мы помещаем ее краткое описание и перечень входных и выходных параметров (если они есть). В нашем примере параметры передаются через регистры.
Файл с макросом для вызова подпрограммы treat_array.
macr2v0.inc
TypeByte EQU 1
TypeWord EQU 2
;---------------------------------------------------------------------
; Макрос вызова подпрограммы обработки массива
; array — имя массива
; len_of_array — количество элементов массива
; pattern — образец
; error_label — метка для перехода по ошибке
;---------------------------------------------------------------------
treat MACRO array, len_of_array, pattern, error_label
TA = TYPE array
IF NOT (TA EQ TypeByte OR TA EQ TypeWord)
%OUT На входе должен быть массив слов или байтов!
%OUT Массив array этому условию не удовлетворяет!
%OUT
EXITM
ENDIF
IF pattern GT 111B
%OUT В образце pattern могут быть установлены
%OUT только три младших бита.
%OUT
EXITM
ENDIF
mov si, OFFSET array ;; В SI — адрес массива
mov cx, len_of_array ;; В CX — количество элементов
mov dx, TA ;; В DX — тип элементов
mov bp, pattern ;; В BP — образец
call treat_array
jc error_label
ENDM
Директива EXITM используется для досрочного выхода из макроса.
Встроенная функция (времени ассемблирования) TYPE возвращает числовой код, соответствующий типу данных: 1 — для байтов, 2 — для слов, 4 — для двойных слов, остальные значения опустим.
Для комментариев используется повтор точки с запятой. Это делается для того, чтобы комментарии макроса не попадали в листинг и не увеличивали его длину.
Наконец, создадим файл с главной программой. Сначала сконструируем данные, на которых будем проверять правильность работы программы.
Нужно образовать массивы, элементы которых таковы, что они либо удовлетворяют либо не удовлетворяют условию задачи, чтобы проверить правильность работы программы во всех возможных ситуациях.
Будем использовать образец, задаваемый как параметр в макросе, 101b. В нашем примере достаточно сконструировать массив всего из двух элементов: для одного элемента условие выполняется, а для другого — нет.
Первый элемент (рис. 18.2):
7 6 5 4 3 2 1 0
1 1 0 1 0 1 0 1
=
установить
Рис. 18.2.
Второй элемент (рис.18.3):
7 6 5 4 3 2 1 0
1 1 1 1 0 1 0 1
сбросить
Рис. 18.3.
Итак, первый элемент 11010101 11010101, т.е. D5h D5h, а второй элемент 11110101 01110101, т.е. F5h 75h. Аналогичный тест предложим для слов: 00D5h 80D5h, 00F5h 00F5h.
main2v0.asm
COMMENT &
0. Иванов И.И. Дан массив байтов (слов) указанного размера.
Над каждым элементом массива выполнить операцию: если битовое
поле 6:4 совпадает с битовым полем 2:0 и с заданным образцом,
то установить старший бит элемента, иначе — сбросить его.
&
JUMPS ; Оптимизация переходов
.286 ; Разрешены инструкции 286-го процессора
INCLUDE macro.inc ; Включить общий файл макросов
INCLUDE macr2v0.inc ; Включить специальный файл макросов
;
GLOBAL treat_array:PROC
.MODEL small
.STACK 100h
.DATA
arrb DB 0D5h,0F5h
Len_of_arrb = $ - arrb
arrw DW 00D5h,00F5h
Len_of_arrw = ($ - arrw) SHR 1
arrd DD 0FFFFFFFFh
Len_of_arrd = ($ - arrd) SHR 2
msg_err DB "Массив пуст!",CRLFT
.CODE
start:
mov ax,@data
mov ds,ax
treat arrb, Len_of_arrb, 101B, error
treat arrw, Len_of_arrw, 101B, error
treat arrw, Len_of_arrw, 1101B, error
treat arrd, Len_of_arrd, 10B, error
exit
error: message msg_err
exit 1
END start
Содержимое файла macro.inc описано ранее.
18.4.3. Выполнение задания на компьютере.
1) Ввод текстов программ. Можно воспользоваться файлами с излагаемым примером, как шаблоном.
2) Трансляция, компоновка, выполнение. Можно просто вручную набрать команды:
c: asmin asm /zi/m/la main2v0
c: asmin asm/zi/m/la sub2v0
c: asmin link/v/m main2v0 sub2v0, a2v0
c: asmin d a2v0.exe
но эти команды наверняка придется набирать многократно, т.к. вряд ли у вас все получится с первого раза. Имеет смысл эти команды записать в командный файл. Но этот путь неэффективен вот по какой причине: предположим, мы уже отладили главную программу (она проще), зачем же каждый раз ее транслировать, хотя она не претерпевает изменений. А если при трансляции возникнет ошибка, то компоновку выполнять не нужно (вообще-то этой ситуации можно избежать, если в командный файл вставить проверку уровня ошибки ERRORLEVEL). Но лучшим решением является использование утилиты MAKE.
Для того чтобы воспользоваться этой утилитой, представим наглядно в виде дерева (рис. 18.4) зависимость итогового файла a2v0.exe от исходных файлов:
a2v0.exe
main2v0.obj sub2v0.obj
main2v0.asm macr2v0.inc sub2v0.asm
Рис. 18.4.
А теперь запишем это дерево в make-файле. Make-файл имеет следующую структуру:
цель: условие
команда
команда
Эти строки могут повторяться многократно.
Цели и условия — списки имен файлов, разделенные пробелами. Команда — это любая команда DOS. Цель обязательно начинается с первой позиции, а команда — с отступом хотя бы на одну позицию.
файл a2v0.mak
a2v0.exe: main2v0.obj sub2v0.obj
c: asmin link /v/m main2v0 sub2v0, a2v0
c: asmin d a2v0
main2v0.obj: main2v0.asm macr2v0.inc
c: asmin asm /zi/m/la main2v0
sub2v0.obj: sub2v0.asm
c: asmin asm /zi/m/la sub2v0
Строки, которые начинаются с первой позиции, соответствуют неконцевым вершинам дерева. После двоеточия перечисляются вершины-потомки (хотя в нашем случае их хотелось бы назвать "предками"). Строки с отступом не менее единицы содержат команды, которые надо выполнить, чтобы получить целевой файл (тот, что перед двоеточием).
Перед ключами (опциями) команд надо обязательно вставлять пробел.
Запуск файла на выполнение осуществляется по команде
c: asminmake -fa2v0.mak (после ключа -f указывается имя make-файла)
Make обрабатывает файл так. Поочередно просматриваются все зависимости. Для каждой зависимости сравниваются даты последнего изменения целей и условий. Если дата/время последнего изменения одного из файлов-условий больше, чем у любой из целей, или если файл-цель отсутствует, make вызывает все команды, указанные в зависимости.
Возможности этой утилиты намного больше, чем здесь показано.
Рекомендуется make запускать так:
c: asminmake -f2v0.mak > a2v0.txt
Тогда вы прочтете отчет о работе make и сообщения запускаемых программ в файле a2v0.txt.
В результате выполнения make-файла на диске помимо исходных файлов (main2v0.asm, sub2v0.asm, macr2v0.asm, macro.inc) появляются файлы:
• main2v0.obj, sub2v0.obj;
• main2v0.lst, sub2v0.lst;
• a2v0.exe;
• a2v0.map.
3) Подготовка отчета.
После запуска Turbo Debugger перед вами два окна Module (1) и Watch (2). Они нам не понадобятся, но закрывать их не будем (хотя, если возникают проблемы с памятью, лишние окна рекомендуется закрывать). Перечислим действия пользователя в отладчике в предположении, что программа работает правильно.
1) Запишем адрес процедуры treat_array в окно протокола. Сейчас активно первое окно (текст главной программы). Выведем текст модуля с подпрограммами. Для этого нажмем F3 (Module) и возникшем меню выберем имя файла с подпрограммами. Подведем курсор в строку treat_array PROC и нажимаем Ctrl+I (Alt+F10/Inspect). Появляется окно, в котором читаем адрес процедуры. Копируем содержимое окна в окно протокола. Alt+W,D (F10/Window/Dump Pane to Log). Замечание: если вы используете более позднюю версию TD, то F10/Edit/Dump Pane to Log.
2) Создадим окно CPU. Alt+V,C,F5,Ctrl+M,Ctrl+M (эквивалент при использовании меню: F10/View/CPU, F10/Window/Zoom, Alt+F10/Mixed/Both).
3) Выполним команды загрузки DS (mov ax,@data / mov ds,ax): F7,F7.
4) В панели данных покажем массив arrb. Shift+Tab, Ctrl+G (Alt+F10/Goto). Набираем имя arrb. В панели данных отображается
ds:0000 D5 F5 D5 00 F5 00 …
5) Скопируем содержимое панели в окно протокола. Alt+W,D (F10/ Window/ Dump Pane to Log).
6) В окне данных покажем массив arrw. Alt+V,D (F10/View/Dump). Получаем 4-ое окно. Ctrl+G (Alt+F10/Goto..). Набираем имя arrw. Отобразим как слова. Ctrl+D,W (Alt+F10/Display as…/Word) . В окне данных отображается
ds:0002 00D5 00F5 …
Скопируем в окно протокола Alt+W,D.
7) Войдем в процедуру treat_elem и скопируем стек. Перейдем в окно CPU (Alt+3, т.е. в окно 3; либо можно нажимать F6 пока не окажетесь в нужном окне). Перейдем в панель кода: Tab. Многократным нажатием на F7 попадаем в процедуру treat_elem (в ней выполним команды push ax, push bx). Переходим в панель стека: Tab,Tab,Tab. Скопируем в окно протокола: Alt+W,D. Вернемся в панель кода (необязательно): Tab,Tab.
8) Выполним программу и скопируем результат в окно протокола. Выполнить до конца: F9 (F10/Run/Run). Получим окно с сообщением: Terminated, exit code 0. Нажимаем Enter. Переходим в панель данных: Shift+Tab. Скопируем в окно протокола: Alt+W,D. Перейдем в 4-ое окно: Alt+4 (F6,F6,F6). Скопируем в окно протокола: Alt+W,D.
9) Сохраним протокол в файле. Перейдем в окно протокола: Alt+V,L (F10/View/Log). Распахнем окно: F5 (F10/Window/Zoom). Откроем файл протокола: Ctrl+O (Alt+F10/Open log file …). Запрашивается имя файла (по умолчанию предлагается a2v0.log) — подтверждаем. Закрываем файл: Ctrl+C (Alt+F10/Close log file).
10) Завершаем работу в TD. Alt+X (F10/File/Quit).
В текстовом редакторе правим файл a2v0.log, распечатываем его и снабжаем комментариями.
Turbo Debugger Log
Inspecting treat_array
@5571:006E
CPU 80286
ds:0000 D5 F5
Dump
ds:0002 00D5 00F5
CPU 80286
ss:0100
ss:00FE 0014 - адрес команды jc error (после call treat_array)
ss:00FC 0079 - адрес команды mov [si],ax (после call treat_elem)
ss:00FA F5D5 - содержимое AX
ss:00F8 0000 - содержимое BX
Terminated, exit code 0
CPU 80286
6356:0000 D5 75
Dump
6356:0002 80D5 00F5
Справа от исходных данных и результата можно вставить комментарий, поясняющий их (те же данные в двоичном представлении, с выделенными полями и т.д.). Что касается стека, желательно, прокручивая окно CPU, найти адреса команд, помещенные в стек.
Адрес программы обработки массива treat_array вы ищете в таблице имен листингов main2v0.lst, sub2v0.lst, а также в карте памяти a2v0.map. Адрес в отладчике мы уже записали в окно Log. Наконец, размер кода и данных программы вы найдете в карте памяти a2v0.map. В отчет нужно включать строчки из этих файлов, вырезанные с помощью текстового редактора.
18.5. Несколько задач.
Задача. Поместить в регистр BX старшее слово регистра EAX
1-е решение
mov cx,16 ; размер слова
m: rol eax,1
rcl bx,1
loop m
rol eax,16 ; восстановить eax
2-е решение
ror eax, 16
mov bx, ax
ror eax, 16 ; восстановить eax
3-е решение
mov ebx, eax
ror ebx, 16
(недостаток: портится старшее слово EBX)
Позднее мы увидим еще одно решение этой задачи: с использованием команды двойного сдвига.
Задача: сосчитать количество единичных битов в eax
xor bx,bx ; Счетчик единичных битов
mov cx,32
n: rol eax,1
adc bx,0
loop n
Задача. Расположить содержимое AX в регистре BX в обратном порядке.
mov cx, 16
n: rol ax,1
rcr bx,1
loop n
18.6. Битовые операции, появившиеся в 386-м процессоре
18.6.1. Команды сканирования битов bsf и bsr.
Сканирование бита вперед bsf dst,src в dst — индекс первого справа единичного бита в src
Bit Scan Forward ZF = 1, если все биты src нулевые
ZF = 0, если в src есть единичный бит
форматы операндов: bsf r16, r/m16, bsf r32, r/m32
Сканирование бита назад bsr dst,src в dst — индекс первого слева единичного бита в src
Bit Scan Reverse ZF = 1, если все биты src нулевые
ZF = 0, если в src есть единичный бит
форматы операндов: bsr r16, r/m16, bsf r32, r/m32
Итак, "вперед" — в направлении увеличения нумерации битов, "назад" — в направлении уменьшения. Индекс — номер разряда (бита).
Пример. Выяснить индекс самого старшего установленного бита в регистре DX (пусть DX содержит число 5).
bsr ax, dx ; ax = 2
Пример. Сдвинуть содержимое регистра EBX вправо так, чтобы младший бит EBX был установлен; если это невозможно, перейти на метку null (Пусть EBX = 84h = 0…0 1000 0100b.)
bsf ecx, ebx ; ecx = 2
jz null
shr ebx, cl ; ebx = 21h = 0…0 0010 0001b
18.6.2. Команды проверки битов bt, bts, btr, btc
Сначала разберем команду bt, остальные команды на нее похожи.
Проверка бита bt src, index сохраняет бит из src с номером index в CF
Bit Test CF = 1, если бит src установлен,
CF = 0, если бит src сброшен
форматы операндов: bt r/m16, r16, bt r/m32, r32, bt r/m16, i8, bt r/m32, i8
Пример. Поместить второй бит регистра CX в флаг CF.
mov cx, 5 ; cx = 101b
bt cx, 2 ; CF = 1
Пример. Решим уже знакомую задачу: сосчитать количество единичных битов в регистре AX.
xor cx, cx
xor bx, bx
n: bt ax, cx
adc bx, 0
inc cx
cmp cx, 16
jnae n
Для оставшихся трех команд не будем приводить подробного описания — оно вполне аналогично команде bt.
btc — проверка и инвертирование (bit test and complement) — бит помещается в CF, сам бит инвертируется.
btr — проверка и сброс (bit test and reset) — бит помещается в CF, сам бит сбрасывается.
bts — проверка и установка (bit test and set) — бит помещается в CF, сам бит устанавливается.
Упражнение. Придумайте примеры для каждой из приведенных команд и проверьте их работу в TD.
18.6.3. Команды двойного сдвига
Двойной сдвиг влево shld dst,src, count описание приведено ниже
Double precision Shift Left SF, ZF, PF по результату;
в CF последний выдвинутый бит;
OF = 1, если произошло изменение знака (имеет смысл при count = 1)
форматы операндов: shld r/m16, r16, i8/cl shld r/m32, r32, i8/cl
dst и src объединяются и происходит сдвиг влево на count бит объединенного содержимого. src остается без изменений.
Аналогично работает команда двойного сдвига вправо shrd dst,src, count.
Пример. Поместить в регистр BX старшее слово регистра EAX
shld ebx,eax,16
если нужно, чтобы старшее слово EBX при этом не изменялось, то
shr ebx,16
shld ebx,eax,16
Пример. Пусть array — массив двойных слов. Осуществить циклический сдвиг всего массива влево на 12 бит
.MODEL small
.386
.STACK 100h
.DATA
array DD 8 DUP(12345678h)
LEN_ARRAY = ($-array) SHR 2
.CODE
start: mov ax, @data
mov ds, ax
mov edx, array
xor esi, esi
mov ecx, LEN_ARRAY - 1
next: mov eax, array+4[esi*4]
shld array[esi*4], eax, 12
inc esi
loop next
shld array[esi*4], edx, 12
mov ax, 4C00h
int 21h
END start
В отладчике мы видим, что теперь элементами массива являются числа 4568123h.
Команда двойного сдвига применяется для быстрого перемещения строки битов на новое место в памяти. (В англоязычной литературе используется сокращение "bit blt" (BIT Block Transfer — перемещение блока бит).
Пример. Вставить битовую подстроку длиной 16 бит в строку битов, начиная с 8-го бита (т.е. заменить биты 23:8 заданной строкой из 16 бит). Вставлямая подстрока выровнена к левому краю. [Юров, с изменениями]
MODEL small
.386
.STACK 100h
.DATA
bit_str DD 12345678h ; строка-приемник
p_str DD 0ABCD0000h ; вставляемая подстрока 0ABCDh
.CODE
start: mov ax, @data
mov ds, ax
mov eax, p_str : Поместить подстроку в EAX
; Правый край места вставки циклически переместить к краю
; строки bit_str (т.е. слева поместить правые биты)
ror bit_str,8 ; (bit_str = 78123456)
; Сдвинуть строку вправо на длину подстроки
shr bit_str,16 ; (bit_str = 00007812)
; Поместить подстроку в строку-приемник (16 – размер подстроки)
shld bit_str,eax,16 : (bit_str = 7812ABCD)
; Восстановить младшие 8 бит (вернуть правые биты на место)
rol bit_str,8 : (bit_str=12ABCD78)
mov ax,4C00h
int 21h
END start
Задача. Переписать эту программу с использованием битовых полей (константы 8 и 16 должны вычисляться на этапе ассемблирования, а bit_str и p_str должны определяться посредством записей).
Задача. С помощью команды двойного сдвига реализовать противоположную задачу: извлечь подстроку 23:8 и поместить ее в EAX, выровняв по правому краю.
18c. Битовые операции, появившиеся в 386-м процессоре
18c.1. Команды сканирования битов bsf и bsr.
Сканирование бита вперед bsf dst,src в dst — индекс первого справа единичного бита в src
Bit Scan Forward ZF = 1, если все биты src нулевые
ZF = 0, если в src есть единичный бит
форматы операндов: bsf r16, r/m16, bsf r32, r/m32
Сканирование бита назад bsr dst,src в dst — индекс первого слева единичного бита в src
Bit Scan Reverse ZF = 1, если все биты src нулевые
ZF = 0, если в src есть единичный бит
форматы операндов: bsr r16, r/m16, bsf r32, r/m32
Итак, "вперед" — в направлении увеличения нумерации битов, "назад" — в направлении уменьшения. Индекс — номер разряда (бита).
Пример. Выяснить индекс самого старшего установленного бита в регистре DX (пусть DX содержит число 5).
bsr ax, dx ; ax = 2
Пример. Сдвинуть содержимое регистра EBX вправо так, чтобы младший бит EBX был установлен; если это невозможно, перейти на метку null (Пусть EBX = 84h = 0…0 1000 0100b.)
bsf ecx, ebx ; ecx = 2
jz null
shr ebx, cl ; ebx = 21h = 0…0 0010 0001b
18c.2. Команды проверки битов bt, bts, btr, btc
Сначала разберем команду bt, остальные команды на нее похожи.
Проверка бита bt src, index сохраняет бит из src с номером index в CF
Bit Test CF = 1, если бит src установлен,
CF = 0, если бит src сброшен
форматы операндов: bt r/m16, r16, bt r/m32, r32, bt r/m16, i8, bt r/m32, i8
Пример. Поместить второй бит регистра CX в флаг CF.
mov cx, 5 ; cx = 101b
bt cx, 2 ; CF = 1
Пример. Решим уже знакомую задачу: сосчитать количество единичных битов в регистре AX.
xor cx, cx
xor bx, bx
n: bt ax, cx
adc bx, 0
inc cx
cmp cx, 16
jnae n
Для оставшихся трех команд не будем приводить подробного описания — оно вполне аналогично команде bt.
btc — проверка и инвертирование (bit test and complement) — бит помещается в CF, сам бит инвертируется.
btr — проверка и сброс (bit test and reset) — бит помещается в CF, сам бит сбрасывается.
bts — проверка и установка (bit test and set) — бит помещается в CF, сам бит устанавливается.
Упражнение. Придумайте примеры для каждой из приведенных команд и проверьте их работу в TD.
18c.3. Команды двойного сдвига
Двойной сдвиг влево shld dst,src, count описание приведено ниже
Double precision Shift Left SF, ZF, PF по результату;
в CF последний выдвинутый бит;
OF = 1, если произошло изменение знака (имеет смысл при count = 1)
форматы операндов: shld r/m16, r16, i8/cl shld r/m32, r32, i8/cl
dst и src объединяются и происходит сдвиг влево на count бит объединенного содержимого. src остается без изменений.
Аналогично работает команда двойного сдвига вправо shrd dst,src, count.
Пример. Поместить в регистр BX старшее слово регистра EAX
shld ebx,eax,16
если нужно, чтобы старшее слово EBX при этом не изменялось, то
shr ebx,16
shld ebx,eax,16
Пример. Пусть array — массив двойных слов. Осуществить циклический сдвиг всего массива влево на 12 бит
.MODEL small
.386
.STACK 100h
.DATA
array DD 8 DUP(12345678h)
LEN_ARRAY = ($-array) SHR 2
.CODE
start: mov ax, @data
mov ds, ax
mov edx, array
xor esi, esi
mov ecx, LEN_ARRAY - 1
next: mov eax, array+4[esi*4]
shld array[esi*4], eax, 12
inc esi
loop next
shld array[esi*4], edx, 12
mov ax, 4C00h
int 21h
END start
В отладчике мы видим, что теперь элементами массива являются числа 4568123h.
Команда двойного сдвига применяется для быстрого перемещения строки битов на новое место в памяти. (В англоязычной литературе используется сокращение "bit blt" (BIT Block Transfer — перемещение блока бит).
Пример. Вставить битовую подстроку длиной 16 бит в строку битов, начиная с 8-го бита (т.е. заменить биты 23:8 заданной строкой из 16 бит). Вставлямая подстрока выровнена к левому краю. [Юров, с изменениями]
MODEL small
.386
.STACK 100h
.DATA
bit_str DD 12345678h ; строка-приемник
p_str DD 0ABCD0000h ; вставляемая подстрока 0ABCDh
.CODE
start: mov ax, @data
mov ds, ax
mov eax, p_str : Поместить подстроку в EAX
; Правый край места вставки циклически переместить к краю
; строки bit_str (т.е. слева поместить правые биты)
ror bit_str,8 ; (bit_str = 78123456)
; Сдвинуть строку вправо на длину подстроки
shr bit_str,16 ; (bit_str = 00007812)
; Поместить подстроку в строку-приемник (16 – размер подстроки)
shld bit_str,eax,16 : (bit_str = 7812ABCD)
; Восстановить младшие 8 бит (вернуть правые биты на место)
rol bit_str,8 : (bit_str=12ABCD78)
mov ax,4C00h
int 21h
END start
Задача. Переписать эту программу с использованием битовых полей (константы 8 и 16 должны вычисляться на этапе ассемблирования, а bit_str и p_str должны определяться посредством записей).
Задача. С помощью команды двойного сдвига реализовать противоположную задачу: извлечь подстроку 23:8 и поместить ее в EAX, выровняв по правому краю.
18. Битовые операции 18.1. Булевские команды
Лекции по предмету «Информатика»