Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на языке ассемблера

.pdf
Скачиваний:
81
Добавлен:
08.05.2015
Размер:
1.66 Mб
Скачать

До начала цикла в регистр ECX необходимо записать число повторений цикла. Команда LOOPE/LOOPZ, как и команда LOOP ставится в конце цикла, а перед ней помещается команда, которая меняет флаг ZF (обычно это команда сравнения CMP). Команда LOOPE/LOOPZ заставляет цикл повторяться ECX раз, но только если предыдущая команда фиксирует равенство сравниваемых величин (вырабатывает нулевой результат, т.е. ZF = 1).

По какой именно причине произошёл выход из цикла надо проверять после цикла. Причём надо проверять флаг ZF, а не регистр ECX, т.к. условиеZF = 0 может появиться как раз на последнем шаге цикла, когда и регистр ECX стал нулевым.

Команда LOOPNE/LOOPNZ аналогична команде LOOPE/LOOPZ, но досрочный выход из цикла осуществляется, если ZF = 1.

Рассмотрим пример: пусть в регистре ESI находится адрес начала некоторого массива двойных слов, а в переменной n – количество элементов массива, требуется проверить наличие в массиве элементов, кратных заданному числу x, и занести в переменную f значение 1, если такие элементы есть, и 0 в противном случае.

mov

ebx, x

mov

ecx, n

mov

f, 1

L1: mov

eax, [esi]

add

esi, 4

cdq

 

idiv

ebx

cmp

edx, 0

loopne L1

je

L2

mov

f, 0

L2:

 

5. Массивы

5.1. Модификация адресов

Как уже было сказано, массивы в языке ассемблера описываются по директивам определения данных с использованием конструкции повторения (см. раздел 2.6). Для того чтобы обратиться к элементу массива, необходимо так или иначе указать адрес начала массива и смещениеэлемента в массиве. Смещение первого элемента массива всегда равно 0. Смещения остальных элементов массива зависят от размера элементов.

Пусть X – некий массив. Тогда адрес элемента массива можно вычислить по следующей формуле:

адрес(X[i]) = X + (type X) * i, где i – номер элемента массива, начинающийся с 0

Напомним, что имя переменной эквивалентно её адресу (для массива – адресу начала массива), а операция type определяет размер переменной (для массива определяется размер элемента массива в соответствии с использованной директивой).

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

x[4]

x[ebx]

Однако принципиальное отличие состоит в том, в программе на языке высокого уровня мы указываем индекс элемента массива, а компилятор умножает его на размер элемента массива, получая смещение элемента массива. В программе на языке ассемблера указывается именно смещение, т.е. программист должен сам учитывать размер элемента массива. Компилятор же языка ассемблера просто прибавляет смещение к указанному адресу. Приведённые выше команды можно записать по-другому:

x + 4

[x + 4]

[x] + [4]

[x][4]

[x + ebx]

[x] + [ebx]

[x][ebx]

Обратите внимание, что при использовании регистра для модификации адреса наличие квадратных скобок обязательно. В противном случае компилятор зафиксирует ошибку.

Адрес может вычисляться и по более сложной схеме:

<база> + <множитель> * <индекс> + <смещение>

База – это регистр или имя переменной. Индекс должен быть записан в некотором регистре. Множитель – это константа 1 (можно опустить), 2, 4 или 8. Смещение – целое положительное или отрицательное число.

mov

eax,

[ebx

+

4

* ecx - 32]

mov

eax,

[x +

2

*

ecx]

5.2. Команда LEA

Команда LEA осуществляет

загрузку

в

регистр

так

называемого эффективного адреса:

 

 

 

 

LEA <регистр>, <ячейка памяти>

 

 

 

 

Команда не меняет флаги. В простейшем случае с помощью команды LEA можно загрузить в регистр адрес переменной или начала массива:

x dd 100 dup (0)

lea ebx, x

Однако поскольку адрес может быть вычислен с использованием операций сложения и умножения, команда LEA имеет также ряд других применений (см. раздел 8.3.2).

5.3. Обработка массивов

Пусть есть массив x и переменная n, хранящая количество элементов этого массива.

x dd 100 dup(?)

n dd ?

Для обработки массива можно использовать несколько способов.

1. В регистре можно хранить смещение элемента массива.

mov eax, 0

mov

ecx, n

mov

ebx, 0

L: add

eax, x[ebx]

add

ebx, type x

dec

ecx

cmp

ecx, 0

jne

L

2.В регистре можно хранить номер элемента массива и умножать его на размер элемента.

mov

eax, 0

mov

ecx, n

L: dec

ecx

add

eax, x[ecx * type x]

cmp

ecx, 0

jne

L

3.В регистре можно хранить адрес элемента массива. Адрес начала массива можно записать в регистр с помощью команды LEA.

mov

eax, 0

mov

ecx, n

lea

ebx, x

L: add

eax, [ebx]

add

ebx, type x

dec

ecx

cmp

ecx, 0

jne

L

4.При необходимости можно в один регистр записать адрес начала массива, а в другой – номер или смещение элемента массива.

mov eax, 0

mov ecx, n

lea ebx, x

L: dec ecx

add

eax, [ebx + ecx * type x]

cmp ecx, 0

jne L

Модификацию адреса можно производить также по двум регистрам: x[ebx][esi]. Это может быть удобно при работе со структурами данных, которые рассматриваются как матрицы. Рассмотрим для примера подсчёт количества строк матриц с положительной суммой элементов.

mov

esi,

0

; Начальное смещение строки

 

mov

ebx,

0

; EBX будет содержать количество

строк, удовлетворяющих условию

 

 

 

 

 

 

mov

ecx,

m

; Загружаем в ECX количество строк

L1: mov

edi,

0

;

Начальное

смещение

элемента в

строке

 

 

 

 

 

 

 

 

 

mov

eax,

0

;

EAX

будет

содержать

сумму

элементов

строки

 

 

 

 

 

 

 

mov

edx,

n

;

Загружаем

в

EDX

количество

элементов

в строке

 

 

 

 

 

 

 

L2: add

eax, y[esi][edi]

 

; Прибавляем к

EAX

элемент

массива

 

 

 

 

 

 

 

 

 

add

edi, type y

 

;

Прибавляем

к

смещению

элемента в строке размер элемента

 

 

 

 

 

dec

edx

 

;

Уменьшаем

 

на

1

счётчик

внутреннего цикла

 

 

 

 

 

 

 

cmp

edx, 0

; Сравниваем EDX с нулём

 

jne

L2

 

; Если EDX не равно 0, то переходим

к началу цикла

 

 

 

 

 

 

 

 

cmp

eax, 0

;

После

цикла

сравниваем сумму

элементов

строки с нулём

 

 

 

 

 

 

 

jle

L3

; Если сумма меньше или равна 0,

то

обходим увеличение EBX

 

 

 

 

 

 

 

 

 

inc

ebx

;

Если

же

сумму

больше

0,

то

увеличиваем EBX

 

 

 

 

 

 

 

 

 

L3: mov

eax, n

;

Загружаем

 

в

EAX

количество

элементов

в строке

 

 

 

 

 

 

 

 

 

imul

eax, type y

 

;

Умножаем

количество

элементов

в строке на размер элемента

 

 

 

 

 

 

 

add

esi, eax

;

Прибавляем

к

смещению

полученный

размер строки

 

 

 

 

 

 

 

 

 

dec

ecx

;

Уменьшаем

на

1

счётчик

внешнего

цикла

 

 

 

 

 

 

 

 

 

 

cmp

ecx, 0

; Сравниваем ECX с нулём

 

 

 

jne

L1

; Если ECX не равно 0, то

переходим

к началу цикла

6. Поразрядные операции

Поразрядные операции реализуют одну и ту же логическую операцию над всеми битами переменной. К поразрядным операциям относят также операции сдвига.

6.1. Логические команды

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

NOT <операнд>

Операция поразрядное «и» выполняет логическое умножение всех пар бит операндов.

AND <операнд1>, <операнд2>

Операция поразрядное «или» выполняет логическое сложение всех пар бит операндов.

OR <операнд1>, <операнд2>

Операция поразрядное исключающее «или» выполняет сложение по модулю 2 всех пар бит операндов.

XOR <операнд1>, <операнд2>

Операции AND, OR и XOR имеют по два операнда. Первый может быть регистром или ячейкой памяти, а второй – регистром, ячейкой памяти или непосредственным операндом. Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда. Операции меняют флаги CF, OF, PF, SF и ZF.

Операция XOR имеет интересную особенность – если значения операндов совпадают, то результатом будет значение 0. Поэтому операцию XORиспользуют для обнуления регистров – она выполняется быстрее, чем запись нуля с помощью команды MOV.

xor

eax, eax

; При любом значении EAX результат

будет

равен 0

 

Операцию XOR можно также использовать для обмена значений двух переменных.

xor

eax, ebx

; EAX = EAX xor EBX

 

xor

ebx, eax

;

Теперь

EBX

содержит

исходное

значение EAX

 

 

 

 

 

xor

eax, ebx

;

А теперь EAX

содержит

исходное

значение EBX

 

 

 

 

 

6.2. Команды сдвига

 

 

 

 

 

Операции сдвига

вправо и сдвига

влево сдвигают

биты в

переменной на заданное количество позиций. Каждая команда сдвига имеет две разновидности:

<мнемокод> <операнд>, <непосредственный операнд>

<мнемокод> <операнд>, CL

Первый операнд должен быть регистром или ячейкой памяти. Именно в нём осуществляется сдвиг. Второй операнд определяет количество позиций для сдвига, которое задаётся непосредственным операндом или хранится в регистре CL (и только CL).

Команды сдвига меняют флаги CF, OF, PF, SF и ZF.

Существует несколько разновидностей сдвигов, которые отличаются тем, как заполняются «освобождающиеся» биты.

6.2.1. Логические сдвиги

При логическом сдвиге «освобождающиеся» биты заполняются нулями. Последний ушедший бит сохраняется во флаге CF.

SHL

<операнд>, <количество>

;

Логический

сдвиг

влево

 

 

 

SHR

<операнд>, <количество>

;

Логический

сдвиг

вправо

 

 

 

6.2.2. Арифметические сдвиги

Арифметический сдвиг влево эквивалентен логическому сдвигу влево (это одна и та же команда) – «освобождающие» биты заполняются нулями. При арифметическом сдвиге вправо «освобождающиеся» биты заполняются знаковым битом. Последний ушедший бит сохраняется во флаге CF.

SAL

<операнд>, <количество>

;

Арифметический

сдвиг

влево

 

 

SAR

<операнд>, <количество>

;

Арифметический

сдвиг

вправо

 

 

6.2.3. Циклические сдвиги

При циклическом сдвиге «освобождающиеся» биты заполняются ушедшими битами. Последний ушедший бит сохраняется во флаге CF.

ROL

<операнд>, <количество>

;

Циклический

сдвиг

влево

 

 

 

ROR

<операнд>, <количество>

;

Циклический

сдвиг

вправо

 

 

 

6.2.4. Расширенные сдвиги

Расширенные сдвиги немного отличаются от остальных сдвигов. В расширенных сдвигах участвуют два регистра или ячейка памяти и регистр, которые как бы объединяются в единое целое и «освобождающиеся» биты одного операнда заполняются битами из другого операнда.

SHLD

<операнд1>, <операнд2>, <количество>

;

Расширенный

сдвиг

влево

 

 

SHRD

<операнд1>, <операнд2>, <количество>

;

Расширенный

сдвиг

вправо

 

 

Команда SHLD сдвигает

влево

биты операнда1 на

указанное

количество

позиций.

Младшие

(«освободившиеся»)

биты операнда1 заполняются

старшими

битами операнда2.

Сам операнд2 не меняется.

 

 

 

 

Команда SHRD сдвигает

вправо

биты операнда1 на

указанное

количество

позиций.

Старшие

(«освободившиеся»)

биты операнда1 заполняются

младшими

битами операнда2.

Сам операнд2 не меняется.

 

 

 

 

Количество, как и в других операциях сдвига, задаётся непосредственным операндом или хранится в регистре CL. Но используются только последние 5 бит операнда, определяющего количество, т.е. максимальное количество позиций сдвига равно 32.

Команды расширенного сдвига обычно используют для создания упакованных данных.

6.3. Умножение и деление с помощью поразрядных операций

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

6.3.1. Умножение

Для умножения используется сдвиг влево. Несмотря на наличие двух команда, по сути, сдвиг влево один. Он используется для умножения как знаковых, так и беззнаковых чисел. Однако результат будет правильным, только в том случае, если он умещается в регистр или ячейку памяти.

mov

ax,

250

;

AX = 00fah = 250

sal

ax,

4

;

Умножение на 24 = 16, AX = 0fa0h =

4000

 

 

 

 

mov

ax,

1

;

AX = 1

sal

ax,

10

;

Умножение на 210, AX = 0400h = 1024

mov

ax,

-48

;

AX

=

ffd0h

=

-48

дополнительном

коде)

 

 

 

 

 

 

 

 

 

 

sal

ax,

2

;

AX

=

ff40h

=

-192

дополнительном

коде)

 

 

 

 

 

 

 

 

 

 

mov

ax,

26812

;

AX = 68bch = 26812

sal

ax,

1

; AX =

d178h = -11912

; Знаковое положительное число перешло в

отрицательное

mov

ax,

32943

;

AX = 80afh = 32943

sal

ax,

2

; AX =

02bch = 700

; Большое беззнаковое число стало гораздо

меньше

Сочетая сдвиги со сложением и вычитанием можно выполнить умножение на любое положительное число. Для умножения на отрицательное число следует добавить команду NEG.

mov

ebx, x

 

mov

eax, ebx

 

sal

eax,

2

 

add

eax,

ebx

; EAX = x * 5

mov

ebx, x

 

mov

eax, ebx

 

sal

eax,

3

 

sub

eax,

ebx

; EAX = x * 7

mov ebx, x mov eax, ebx