Программирование на Ассемблере

         

Основные определения


Системное программное обеспечение (СПО)– комплекс программ для увеличения производительности  вычислительной системы и пользователя. Примером СПО является операционная система. Компонентом СПО является системная программа.



Цель курса.


·        Изучить языки для написания системных программ.

·        Изучить приемы и методы создания системных программ



Характеристика языков системного программирования


Язык системного программирования должен удовлетворять следующим свойствам:

·        должен обеспечить создание эффективной программы по требуемым ресурсам (памяти, времени процессора, дисковое пространство)

·        обеспечить возможность использования всех особенностей компьютера, например доступ ко всей оперативной памяти, работу в защищенном режиме, использование и управление кэш - памятью и т.д.

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

·        должен быть надежным и наглядным для уменьшения вероятности пропуска ошибок в программе.

В настоящее время нет языка, полностью удовлетворяющего этим свойствам. Всем требованиям, кроме последнего, удовлетворяет машинный язык и близкий к нему язык Ассемблера. Последнему требованию удовлетворяют языки высокого уровня, но они не удовлетворяют первым трем требованиям, поэтому при создании системных программ используют и язык Ассемблер и язык высокого уровня (язык С, С++). И, хотя для систем, поддерживающих работу с процессорами разных типов, например, WINDOWS NT, драйверы пишутся на языке высокого уровня, значимость ассемблера не падает, так как знание принципов выполнения команд и их хранение в памяти помогает писать «хорошие» программы на любом языке.



Структура 4-х адресной команды




Код операции

1 данное

2 данное

Результат

Адрес следующей команды

Большинство команд выполняетя в том порядке, в котором они записаны в памяти (естественный порядок выполнения команды), поэтому задание четвертого адреса в большинстве команд не требуется. Так как переменная адресность для первых компьютеров не поддерживалась, вместо 4-х адресных стали использовать команды 3-х адресные. Структура 3-х адресной команды

Код команды

1 данное

2 данное

Результат

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

С увеличением общей памяти увеличивается размер адреса, т.к. необходимо под адрес отводить место, достаточное для записи максимального адреса.  В этом случае увеличивается размер команды и всей программы. Кроме того при составлении программ очень часто результат записывается вместо одного из исходных данных. В этом случае адреса результата и одного из данных совпадают и можно задать только один адрес. Получаем 2-х адресную структуру команды.



Структура 2-х адресной команды


Код операции

1 данное  ( результат)

2 данное

Или

Код операции

1 данное

2 данное  (результат)

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

Заметим, что операторы языка С вида <Переменная> <Знак операции> <Выражение> как раз отражают эту ситуацию и программируются одной командой после вычисления <Выражения>. Оператор вида <Переменная> = <Переменная> <Знак операции> <Выражение>  программируется несколькими командами (почему?).

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



Структура 1 адресной команды.


Код операции

Адрес данного

Заметим, что некоторые команды требуют задать только один адрес без использования аккумулятора, например, команда увеличения на 1 содержимого памяти.

 Так оператор С x++ означает использование одноадресной команды вместо двухадресной сложения (x+=1) или нескольких команд в случае x=x+1.

В систему команд должны быть добавлены команды для обмена данными между аккумулятором и памятью. Недостаток одноадресных команд - основную часть программы составляют команды пересылки данных.

В современных процессорах вместо одной фиксированной ячейки (регистра) используется несколько, в этом случае в команде задается адрес данного и регистр, т.е. опять возвращаемся к двух адресной структуре команды.



Структура безадресных команд


Некоторые из команд не требуют задания адреса, например, команда СТОП (Halt) для процессора. В случае использования команд с данными можно использовать  стек для хранения данных, в этом случае адреса данных можно не задавать. Стек – это массив, заполнение и извлечение данных для которого выполняется по правилу «Первый вошел, последний вышел». В этом случае данные для операции записываются в стек. Результат помещается вместо этих данных.



Сравнительный анализ команд с различной адресностью для современных процессоров


Современные процессоры имеют переменную адресность команд. Анализ структур команды современных процессоров показывает, что большинство команд  двухадресные. Некоторые команды могут быть одноадресными (x++) или трех адресными (например, команда длинного сдвига), а иногда и безадресными (команды FPU). Вместо адресов данных могут использоваться константы (литералы).  В качестве адресов могут задаваться регистры, в этом случае скорость выполнения команд увеличивается. Большинство современных процессоров требует, чтобы одно из данных в двух адресной структуре команды было в регистре обязательно, это сокращает длину команды и время ее выполнения.

 Для экономии памяти под команды код команды имеет переменную длину от 4 бит до 2-х байт для процессоров INTEL



История развития ассемблера. Характеристика машинного языка


Первые компьютеры «знали» один язык – машинный. Рассмотрим характеристики этого языка. Конструкциями машинного языка являются константы и команды. Команды содержат код команды и адреса данных, которые используются в командах.



Система команд


Пусть используется 3-х адресная система команд с кодами операций, заданными в табл. 1.1

Таблица 1.1 Система команд для 3-х адресной машины

Операция

Код

+

1

-

2

*

3

/

4



Распределение памяти


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

Таблица  1.2 Распределение памяти

Переменная

Адрес памяти

X

0

Y

1

Z

2

U

3

V

4

W

5



Программа вычисления


Программа представлена в табл. 1.3

Таблица 3.1 Программа для 3-х адресной машины

Адрес

Код

1 данное

2 данное

Результат

Комментарий

6

01

0

1

20

X + Y ®20

7

02

20

2

21

X+Y-Z®21

8

03

21

3

21

(X+Y-Z)*U®21

9

01

21

4

21

(X+Y-Z)*U + V®21

10

04

21

20

5

Результат

Для эффективного распределения памяти программа должна быть переписана с использованием 11 и 12 ячеек в качестве промежуточных.



Пример программирования в машинных кодах.


Пусть необходимо составить программу для вычисления значения выражения:

W = ((X + Y – Z) * U + V)/(X + Y).



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


Для упрощения распределения памяти и запоминания кодов команд вместо машинных кодов используются их обозначения, а вместо конкретных адресов – символические адреса. Символические коды  для основных операций заданы в табл. 4.1

Таблица 4.1 Мнемонические коды арифметических команд

Код

Обозначение

01 (+)

ADD

02 (-)

SUB

03 (*)

MUL

04 (/)

DIV

Пусть данные занимают ячейки

D + 0 : X    D + 1:  Y    D + 2 : Z     D + 3 : U    D + 4 : V   

D + 5 : W   

Пусть программа занимает ячейки P + 0, P + 1, …

Пусть в качестве ячеек для промежуточных данных используются R + 0, R + 1, …

Текст  программы с учетом принятых обозначений задан в табл. 5.1.

Таблица 5.1. Текст  программы

Адрес

Код

1 данное

2 данное

Результат

Комментарий

P+0

ADD

D+0

D+1

R+0

X + Y ®R+0

P+1

SUB

R+0

D+2

R+1

X+Y-Z®R+1

P+2

MUL

R+1

D+3

R+1

(X+Y-Z)*U®R+1

P+3

ADD

R+!

D+4

R+1

(X+Y-Z)*U + V®R+1

P+4

DIV

R+1

R+0

D+5

Результат

Пусть   D= 0. В этом случае программа начинается с ячейки после D + 5, т.е. с ячейки 6. (P = 6). Промежуточные данные можно располагать, начиная с  P + 5,  т.е. R = 11.  Преобразование кодов и адресов в машинные коды и адреса выполняется специальной программой.

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

Ассемблер – язык, для которого одна команда преобразуется в одну команду машинного языка. Исключением являются   макросы, в этом случае одной макрокоманде может соответствовать более одной машинной команды.

Для современных компиляторов вместо относительных адресов можно использовать обозначение переменных, например  ADD X, Y, R. Для вычисления адреса можно использовать более сложные выражения



Пример программы на ассемблере для 16 битного приложения (DOS приложение)


ideal

MODEL SMALL

DATASEG

; Распределение памяти

X                      DW      5; Исходные данные

Y                      DW      6

Z                      DW      ?; Результат

CODESEG

; Программа

begin:

; Связь с данными

mov     ax,  @data

mov     ds,  ax

; Выполнение требуемых операций

mov     ax, [x]

add      ax,[y]

mov     [z], ax

; Выход из  программы

mov     ax, 4c00h

int        21h

;Конец программы

end      begin

Трансляция, компоновка и загрузка в отладчик программы выполняется с помощью команд:

TASM                   /zi   <Имя>.asm

TLINK        /v    < Имя >.obj

TD                        < Имя >

При написании командного файла предполагается, что программы для обработки файла (tasm, tlink, td) находятся в доступном каталоге. Недостатки приведенного командного файла:

·        несмотря на наличие ошибок, все этапы выполняются;

·        командный файл можно использовать только для работы с заданным файлом (с заданным именем).

Для исправления этих недостатков рекомендуем использовать командный файл:

TASM         /zi   %1

IF ERRORLEVEL 1 GOTO m1

TLINK        /v    %1

IF ERRORLEVEL 1 GOTO m1

TD     %1

:m1

Оператор IF ERRORLEVEL 1 GOTO m1 означает, что выполняется переход на метку m1, если код возврата исполняемой программы равен 1 или выше. Все системные программы возвращают 0 при успешном завершения и число, не равное 0 для завершения с ошибками[1]. Поэтому данная проверка позволяет проверить наличие ошибок при компиляции и компоновке. Обратите внимание на синтаксис записи метки на языке команд операционной системы!



Пример программы на ассемблере для 32 - битного приложения


ideal

p586

MODEL FLAT

EXTRN ExitProcess : proc

DATASEG

X                      DW      5

Y                      DW      6

Z                      DW      ?

CODESEG

begin:

mov     ax, X

add      ax,Y

mov     Z, ax

call      ExitProcess

end      begin

Модель  FLAT соответствует 32-битному режиму, когда для задания адреса используется 32-битное число, и диапазон изменения адреса равен 0.. 232-1. Эта модель работает только для 32 битных процессоров, что задается директивой p586.Программа использует функцию операционной системы для завершения программы. Для выполнения этой операции используется функция ExitProcess. Все функции операционной системы (функции WINDOWS API) откомпилированы в регистро – чувствительном режиме. 

Командный файл для 32 – битного режима:

TASM32     /ml  /zi   %1

IF ERRORLEVEL 1 GOTO m1

TLINK32    /v   %1  import32.lib

IF ERRORLEVEL 1 GOTO m1

TD32 %1

:m1

Заметим, что регистро - чувствительная компоновка (флаг /ml) для 32-битного варианта обязательна. Функция ExitProcess предназначена для завершения процесса.

Как показывает сравнительный анализ программ существуют незначительные отличия для программ разных типов. В данном курсе будут рассматриваться программы для 32-битных приложений.



Арифметико-логический блок


Основной характеристикой процессора является его частота. Частота определяет время выполнения одного такта, которое обычно соответствует времени выполнения одной простой инструкции. Например, если частота процессора f =500 мГц (число взято для удобства вычислений) то время выполнения одного такта равно 1/f, т.е. 2 нС (1мГц = 106 гЦ, 1нС = 10-9 с).

Для увеличения быстродействия процессоров используется 2 способа: конвейерная обработка и применение нескольких исполнительных устройств.

Конвейерная обработка состоит в том, что команда делится на независимые части, которые могут исполняться различными модулями. Например, в качестве модулей могут использоваться: модуль выборки очередной команды программы, модуль анализа ее кода, модуль вычисления адресов данных, модуль исполнения, модуль записи результата. Такие 5 модулей имеют конвейеры для 486 процессора и  PENTIUM. Деление на модули выполняется так, чтобы время выполнения каждого модуля было примерно одинаковым. Обозначим время выполнения всех операций для одной команды (длительность такта машины) через t., тогда, если нужно выполнить 10 команд, то потребуется не 10 t ед, а только t + 9 * t/5 ед. времени. Для общего случая, если необходимо выполнить N команд при наличии K устройств, получим формулу для вычисления требуемого времени t + (N - 1) * t/k.

Для PENTIUM 2 уже используется 12 устройств, PENTIUM 3 – 14 таких устройств.

Начиная с PENTIUM, для исполнения команды применяется несколько исполнительных устройств (ИУ), которые независимо могут выполнять команды. В этом случае фактически одновременно выполняется несколько команд и такие процессоры называют суперскалярными. Команды, которые выполняются одновременно, называют спаренными командами. Если обозначить через L количество таких устройств, то время выполнения программы, состоящей из N команд теоретически равно (t + (N - 1) * t/k)/L Почему теоретически? Дело в том, что не все команды можно спаривать, дополнительные ИУ могут выполнять только простые команды. Для Pentium используется 2 исполнительных устройства, для Pentium II и выше – 3. Таким образом, длительность вычислений тем ближе к формуле, чем больше команд спариваются, т.е. последовательность команд существенно влияет на время выполнения программы. Для Pentium последовательность исполняемых команд целиком определяется программистом, есть специальные программы, например, программа VTUNE, которая позволяет определить длительность выполнения участка программы и отследить ситуации, которые влияют на эту длительность. Для Pentium II и выше команда делится на микрокоманды и по мере готовности к выполнению складывается в буфер готовых микрокоманд. Специальный модуль извлекает эти микрокоманды в произвольном порядке, исполняет их и результат записывает в модуль перестановки, который заботится о правильном порядке записи результатов.   Рассмотрим примеры. Пусть необходимо выполнить операторы:


x=5;
y=x+3;
z=4;
u=z-7;
Пусть процессор (условно) может выполнять 2 оператора параллельно и время выполнения одного оператора равно T. В этом случае параллельно можно выполнить 2 и 3 операторы и всего требуется 3T времени.
Если последовательность операторов будет такой:
x=5;
z=4;
y=x+3;
u=z-7;
то потребуется 2T времени. На Pentium требуется перестановка вручную, последующие Pentium сделают это сами, т.к. оператор y=x+3; не появится в буфере готовых команд, а последующий оператор появится. Такая ситуация называется зависимостью по результату, т.е. результат выполнения текущей команды используется в следующей команде. Заметим, что наряду с истинными зависимостями могут быть мнимые зависимости, например:
mov     eax, x
mov     z, eax
mov     eax, y
mov     t, eax
В этой последовательности команд регистр eax используется в качестве буфера для хранения данного. Начиная с Pentium 11, наряду с обычными регистрами,  используются внутренние регистры. Так, для первой и третьей команд будут использованы разные внутренние регистры и они исполняются параллельно. Количество внутренних регистров равно 40, распределением этих регистров и построением таблицы соответствия занимается один из модулей ИУ процессора. Таким образом, для исполнения этих команд потребуется только 2 такта.

Прогнозирование переходов


В случае наличия команд перехода результат предварительного исполнения команд часто должен быть аннулирован, необходимо обработать другую группу команд. Вот почему рекомендуется уменьшать число команд перехода не только при программировании на любом языке программирования! Для увеличения вероятности правильной выборки команд используется блок предсказания переходов (БПП).

БПП появились, начиная с PENTIUM.

Различают два способа прогнозирования переходов: статическое и динамическое. Статическое прогнозирование выполняется, если команда перехода встретилась в программе в первый раз, динамическое – при повторном выполнении команды перехода (цикл, вызов процедуры). Для PENTIUM статическое предсказание для команд безусловного перехода – переход будет, для команд условного перехода – перехода не будет. Для старших версий PENTIUM команды условного перехода делятся на ссылки вперед и назад. Прогнозируется, что ссылка вперед будет, назад – нет. Подумайте, почему выбрана именно такая стратегия предсказания!

Примеры[2]:

s= i=0;

m1:

s+= x[i];

i++;

if (i<10)goto m1;

Для PENTIUM данный переход при первом исполнении цикла будет спрогнозирован неверно, для PENTIUM 11 верно.

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

Для каждой команды перехода, для которой нет еще информации в буфере, она записывается в одну строку, прогноз на переход строится в соответствии со статистическим прогнозом. Если прогнозируется наличие перехода, один из флагов устанавливается в 1. Если фактически перехода нет, этот бит сбрасывается.

При повторном выполнении команды перехода прогнозируется наличие перехода, если хотя бы один флаг установлен. Если фактически переход есть, то второй флаг устанавливается в 1, если нет – единичный флаг сбрасывается. Прогнозируется отсутствие перехода, если оба бита нулевые. Таким образом, если был переход дважды или более, то случайное отсутствие перехода не влияет на правильность прогноза. Наиболее неблагоприятный вариант – чередование наличия и отсутствия перехода.

Начиная с PENTIUM PRO, и для всех последующих процессоров, задается 4 адреса для каждой команды перехода и 4 флага, что позволяет точно предсказывать переход, если период не более 4, например, для фрагмента кода

if (a == 5) a = 7; else  a = 5;

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

Несмотря на мощность БПП, ошибки все же возможны. Для исключения таких ошибок используется так называемое спекулятивное выполнение. Выполняются обе ветви программы. Как только адрес перехода будет вычислен, результаты для неверной ветви аннулируются. Это не совсем безобидный способ - один канал занят выполнением ненужных команд.



Регистровая память.


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

Регистры делятся на

регистры общего назначения (РОН);

сегментные регистры (СР).

управляющие регистры (УР);

отладочные регистры (ОР)

РОН предназначены  для хранения исходных данных для некоторых команд (например, для команды деления содержат делимое), для хранения результатов (команда умножения формирует произведение), исходных и промежуточных данных для команд, в которых одно значение должно быть в регистре (команды пересылки, арифметические команды, …), а также используются для доступа к элементам массивов. Напоминаем о наличии внутренних регистров для каждого общего регистра. Используется 8 РОН, каждый регистр длиной 4 байта. Все регистры можно использовать как целиком, так и их части. Ниже представлены все возможные варианты использования регистра EAX:

AH (1 байт)

AL(1 байт)

AX(2 байта)

EAX(4 байта)

Аналогично используются регистры EBX, ECX, EDX.

Для регистров ESI, EDI, ESP, EBP применяется 2 варианта – целиком весь регистр длиной 4 байта, или его младшие 2 байта (SI, DI, SP, BP). Рекомендуем выписать все возможные регистры на Вашу «Шпаргалку»!

EAX (AX (AL, AH));

EBX (BX (BL, BH));

ECX (CX (CL, CH));

EDX (DX (DL, DH));

ESI (SI);

EDI (DI);

ESP (SP);

EBP (BP).

Буква L в обозначении регистра соответствует младшему байту (LOW), а буква H – старшему (HIGH).

CР предназначены для определения адресов.  Механизм вычисления адреса зависит от режима работы. При программировании в 32-битном режиме для FLAT модели в явном виде не используются. Более подробно сегментные регистры рассмотрены в 3.4.1

УР предназначены для управления разветвлениями (регистр флагов), управления режимами работы процессора(CR0, CR1, …).

Отладочные регистры (DR0, DR1, …) используются для отладки программ.

Используются 2 режима работы процессора: реальный и защищенный. Реальный режим включается автоматически по нажатии клавиши RESET или включении питания. Однозадачный режим. Для задания адреса памяти используется выражение:

(Сегментный регистр * 16) + смещение,

где содержимое сегментного регистра и смещение – 16 битные числа. Диапазон изменения адреса 0..0xffff*0x10 + 0xffff, т.е. немного более одного мегабайта. Защищенный режим будет рассмотрен ниже.

Для работы FPU используются выделенные регистры (ST0, ST1,… ST7), для которых применяется стековая организация.

Для работы блока MMX используются регистры MM0..MM7. Для процессоров PENTIUM и PENTIUM П регистры FPU и    MMX фактически используют один и тот же модуль, поэтому одновременно нельзя использовать команды MMX и FPU. Для PENTIUM Ш это ограничение снято.



Кэш память


Кэш память позволяет значительно ускорить доступ к памяти. Используется кэш память первого и второго уровней (внутренний и внешний кэш). Кэш память первого уровня расположена внутри процессора, время доступа к ней значительно быстрее, чем к другой памяти. Для 486 процессора используется общая кэш память для данных и программы (неразделенная кэш память). Размер кэш памяти 8к. Процессор выполняет команды и работает с данными из кэша. Если данное (команда) не находятся в кэш  памяти, оно загружается, при этом требуется дополнительное время. Ввиду ограниченного размера кэш памяти, штрафы (промахи кэша), связанные с отсутствием данных (команд),  достаточно часто возникают.  Для PENTIUM используется разделенная кэш память, т.е. отдельный кэш для команд и данных. Для PENTIUM размер внутреннего кэша 16 кб. Для PENTIUM PRO внешний кеш расположен на одной системной плате с процессором. Размер кэша для PENTIUM PRO может достигать 512 килобайт. Для доступа к памяти используется 64 битная шина для PENTIUM и 128 битная для PENTIUM PRO.



Память


Как известно, команды и данные находятся в памяти. Быстродействие оперативной памяти (время доступа в среднем равно несколько десятков наносекунд) в значительной степени уступает быстродействию процессора. Для решения задачи используется:

·        регистровая память;

·        кэш (cache) память.



Блок с плавающей точкой


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



Особенности MMX процессоров


MMX (MyltiMedia eXtention – мультимедиа расширение) ориентирован на эффективную обработку изображений и звука. Т.к. при решении этого класса задач приходится выполнять идентичные операции для больших массивов данных, соответствующие процессоры первоначально создавались для эффективного выполнения матричных вычислений и соответствующая аббревиатура MMX расшифровывалась как matrix-multiplication extensions. Для таких процессоров в режиме MMX добавлены специальные SIMD (Single Instraction -> Multiple Data -множество данных - одна инструкция) команды для эффективной работы. MMX процессоры, основанные на PENTIUM, имеют двойной внутренний кэш (32кб). Основная часть операций по обработке изображений переносится на процессор.

Краткая историческая справка. В связи с появлением мультимедиа технологии (в системах используются сложные графические изображения и звук) для получения хорошего качества значительно возросли требования к процессору. Т.к. быстро эти требования не могли быть выполнены, параллельно усовершенствовалась аппаратура для поддержки этих компонент (видеокарта, звуковая карта). Если раньше для получения координат каждой точки и ее цвета было обращение к процессору, то специальные акселераторы уже могли самостоятельно создавать некоторые изображения типа линии, прямоугольника, закрашивать их и т.д. Затем появились интеллектуальные акселераторы, которые могли выполнить требуемые расчеты для изображений и звука, в том числе для объемных изображений. Использование специальных аппаратных средств значительно удорожили ПК.

После появления нового класса ПК (PENTIUM, PENTIUM PRO) процессор обеспечил возможность выполнения большинства требуемых команд с нужной скоростью. Использование же 3D плат задерживает работу шины. Поэтому решается проблема создания ускоренного порта для работы с 3D акселераторами (Accelerated Graphics Port - AGP).



Перспективы разработки процессоров


Основное направление - создание 64 битных процессоров. Линия таких процессоров для архитектуры INTEL называется IA-64 или MERCED. Первый процессор этого типа должен появиться в 2000 году. Основное отличие - использование истинного параллелелизма (Explicitly Parallel  Instruction Computing - EPIC). Компилятор должен находить код, который может выполняться параллельно - проблемы организации вычислений.

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



ВВЕДЕНИЕ В АРХИТЕКТУРУ INTEL ПРОЦЕССОРОВ


 

Архитектура – это структура процессора с точки зрение выполняемых функций.



Классификация простейших конструкций


К простейшим конструкциям языка относятся конструкции, которые используются при построении операторов языка. Простейшие конструкции ассемблера включают в себя:

константы;

переменные;

адреса;

выражения.

Рассмотрим задание каждого типа простейших конструкций



Целые двоичные данные


Целые двоичные данные могут занимать 1, 2, 4, 6, 8 байтов в зависимости от оператора, в котором используется эта константа. Предельные значения константы зависят от ее длины. Целые константы задаются в десятичной, двоичной, восьмеричной или шестнадцатеричной системах счисления. По умолчанию используется десятичная система счисления, пример: 23, 769, -375.

Для определения констант в заданной системе счисления используются суффиксы:

B(b)   - двоичная система счисления (1011b, 0111111111111B);

O(o), Q(q)   - восьмеричная система счисления (1011o, 716543O, 1234 Q);

D(d)  - десятичная система счисления (1011d, 23d, 769d, -375D);

H(h)  - шестнадцатиричная система счисления (1011h, 23H, 0FFFFFFFFh).

Заметим, что маленькая и заглавная буквы для обозначения системы счисления равноправны. При задании шестнадцатеричной системы счисления, если число начинается с буквы (последний пример), то перед буквой должна быть цифра 0. Это необходимо для различения констант и переменных, которые начинаются с буквы.

Если большая часть констант программы должна быть задана в системе счисления, отличной от десятичной, изменяют систему счисления, принятую по умолчанию.  Для этого используется директива RADIX. Общий вид директивы:

RADIX Система счисления,

где Система счисления – константа, которая всегда определяется в десятичной системе счисления. В качестве системы счисления можно задавать значения: 2, 8, 10, 16 . Директива RADIX может использоваться в программе многократно, в этом случае областью действия директивы является программный код до очередной директивы RADIX или до конца программы. Использование суффикса является более приоритетным по сравнению с использованием директивы RADIX. Например, константа 123D является десятичной независимо от предыдущей директивы RADIX. Чтобы данная константа была шестнадцатеричной, для нее необходимо записать 123DH. Целые двоичные данные можно использовать в командах, например

MOV EAX,  123DH

или директивах определения констант и данных, например:

A       dd      123DH

В памяти константы записываются всегда в двоичной системе счисления.

Недостаток двоичных данных: время преобразования в двоичную систему счисления большого числа данные может быть существенным. Поэтому в системах управления базами данных, используемых, как правило,  для обработки большого числа данных,  использование двоичных данных ограничено.



Целые десятичные числа


Числа задаются в памяти в двоично-десятичном коде (BCD-код). Для определения таких констант в программе используется специальная директива (dt), которая задает двоично-десятичную константу в десяти байтах или используется для задания 16-ый код, например, для задания числа 23

используется форма записи 23H. Наряду с рассмотренным форматом, называемым упакованным форматом, когда для каждой десятичной цифры используется 4 бита, применяют так называемый распакованный формат числа, когда для каждой цифры используется один байт, старшие биты заполняются нулями, например, для задания числа 23 в распакованном формате можно использовать запись 0203H.

Достоинства BCD-кода:

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

·        Внутреннее представление может быть получено для данных произвольной длины, для целых данных эта длина может быть равной 1, 2, 4, 6, 8 байтов.

Недостатки BCD-кода:

·        Для одного и того же числового значения требуется больше памяти, чем для двоичного представления. В двоичной системе счисления с помощью одного байта можно задать число от 0 до 255 включительно.    В десятичном коде  можно задать максимальную константу 99

для упакованного и константу 9

для распакованного форматов.

·        Для упакованных и распакованных данных определены не все арифметические действия, например, для данных в упакованном формате определены только операции сложения и вычитания, для распакованных данных – все операции, но только для одной цифры. После выполнения операций для BCD данных необходима корректировка результата. Для двоичных данных определены все операции для 1-4 байтовых данных. Полученные результаты корректировки не требуют.

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



Константы с плавающей точкой


Задаются точно так же, как на языках высокого уровня, например С.  Можно использовать две формы представления:

·        с десятичной точкой, например 1.234, 3.12345678, 34567:

·        в нормальной форме, например 1.E-3, 5.3E27.

По умолчанию для константы отводится 8 байтов. Если константа задается в директиве определения константы, ее длина определяется директивой, например:

dd        5.3E27;          4 байта                      -float

db        5.3E27 ;           1 байт.                       -Ощибка

dw       5.3E27 ;           2 байта                      -Ощибка

dq        5.3E27 ;           8 байтов                    -double

dt         5.3E27 ;           10 байтов                  -long double

Ошибки связаны с тем, что для константы отведена недопустимая длина.



Двухбайтовая кодировка


В 1988 году фирмы APPLE и XEROX предложили 2-х символьную (широкую) кодировку. В 1991 году - комитет по разработке стандартов утвердил Стандарт на символы UNICODE. В предыдущих версиях расширенные символы кодировались так, что младший байт был равен 0, это было признаком двух байтовой кодировки (например, кодировка функциональных клавиш F1 –F12). В UNICODE каждый символ кодируется 2 байтами, поэтому не надо анализировать первый байт, чтобы узнать назначение второго - сейчас в стандарт UNICODE входит »34000 символов. Для программистов оставлено »6000 кодов. В табл. 3.1 представлены коды символов в UNICODE.

Таблица 3.1 Коды символов для UNICODE

 

16-битный код

Символы

0000 - 007F

ASCII

0080 – 00FF

Символы LATIN 1

0100 - 017F

Европейские латинские

0180 – 01FF

Расширенные латинские

0250 – 02AF

Стандартные фонетические

02b0 – 02ff

Модифицированные литеры

0300 - 036f

Общие диакритические знаки

0400 – 04ff

Греческий

0530 - 058f

Кириллица

0590 – 05ff

Армянский

0600 – 06ff

Еврейский

0900 - 097f

Девангари

Достоинства использования UNICODE:

·        возможность создания многоязычных документов;

·        один exe или dll файл для разных языков;

·        увеличивается эффективность прикладных программ.

WINDOWS NT полностью построена на основе UNICODE. Если функции передается обычная строка, она сначала преобразуется в строку типа UNICODE (автоматически). Так как преобразование требует дополнительной памяти и времени, лучше сразу работать со строками типа UNICODE. В WINDOWS 95 не заложена работа с UNICODE, вся внутренняя работа основана на одно символьной кодировке, поэтому все 2-х символьные строки преобразуются в одно символьные.

В ассемблере нет специального обозначения[3]

для расширенной кодировки, поэтому мы используем для задания таких строк данные типа DW, что соответствует 2-х байтовым данным. Так строка «Hello» при двухбайтовой кодировке имеет вид:

Msg   dw     “H”, “e”, “l”, “l”,”o”



Однобайтовая кодировка


Символ задается в кавычках (одинарных или двойных), например:

SymbolD     DB    ‘D’   

SymbolA     DB    ‘A’    

MOV AL, ‘D’; Символ D записывается в регистр AL.

MOV BL, ”A”; Символ A записывается в регистр BL.

MOV BH, SymbolA;  Символ A записывается в регистр BL.

Если необходимо задать строку, состоящую из нескольких символов, используется запись вида:

Msg   db      ‘Hello, World’

Вместо одинарных кавычек можно использовать двойные. Если необходимо задать строку с нулевым завершителем, строка имеет вид:

Msg   db      ‘Hello, World’, 0

Для символов можно задать их код, например:

Msg   db      ‘Hello’, 13, 10, ‘World’, 13, 10, 0

Символы 13, 10 обеспечивают переход в начало очередной строки.



Символьные и строковые константы


Под символ отводится один или два байта. Один байт выделяется при использовании ASCII кодировки, два байта – UNICODE. Рассмотри сначала кодировку данных однобайтную, а затем двух байтную



Константы


Константы делятся на арифметические и строчные. К арифметическим константам относят целые числа и числа с плавающей точкой. К строчным данным относятся данные, включающие в себя один или несколько символов.



Идентификаторы


Используются для обозначения:

·        Переменных;

·        Констант;

·        Функций.

Идентификатором является запись, которая начинается с буквы или символа подчеркивания, в качестве остальных символов могут использоваться латинские буквы, цифры или символ подчеркивания. Количество символов идентификатора – один и больше. Максимальное количество символов не ограничено. Ограничено количество значащих символов (по умолчанию 255). Количество значащих символов идентификатора можно изменить с помощью флага mv#, в котором записывается максимальное количество символов идентификатора. Минимальное количество значащих символов равно 15. 



Внутреннее представление адреса для реального режима


Адрес определяется двумя компонентами: сегмент и смещение. Такой адрес называется двух компонентным. Двух компонентный адрес используется для обеспечения возможности перемещения программы в памяти. Сегмент определяет адрес начала программы в памяти, а смещение задает ее смещение относительно начального адреса. Для реального режима сегмент и смещение задаются 16 - разрядными числами, текущий адрес определяется по формуле:

Сегмент * 16 + Смещение.

Заметим, что изменение содержимого сегментного компонента изменяет адрес. Сегментный компонент записывается в сегментный регистр. По умолчанию для задания адреса кода используется регистр CS, для задания адреса данных - регистр DS, а адреса стека -  SS. Для обозначения данных можно также использовать сегментные регистры ES, GS, FS.

Определим диапазон изменения адреса для реального режима. Максимально возможный адрес 0FFFFH. Максимально возможное смещение равно 0FFFFH. Таким образом, максимально возможный адрес равен

0FFFF0H + 00FFFFH = 10FFEFH.

При разработке первых процессоров казалось, что адресного пространства до 0FFFFFH будет вполне достаточно, это адресное пространство соответствует 1 мГб, поэтому говорят, что в реальном режиме адресуется 1 мГб памяти. Более того, адресное пространство, начиная  с адреса 0FE00H  было выделено под BIOS (Basic Input / Output System), содержащее константы и стандартные функции для работы с внешними устройствами. BIOS позволяет сделать независимым программирование для внешних устройств разных производителей. Адресное пространство 0A0000H .. 0C0000H выделено для видео памяти, т.е. для хранения содержимого на мониторе, а начиная с 0C0000H  для дополнительных модулей BIOS. Таким образом, для программ остается адресное пространство 0 .. 9FFFFH. Размер этого пространства 10 * 65535 = 655350 байт, т.е. 640 Кб. Сегментный компонент всегда записывается в сегментный регистр.

Недостатки адресации в реальном режиме:

·        не позволяют работать со всей оперативной памятью для современных компьютеров;

·        максимальный размер сегмента 64 кБ;

·        нет защиты памяти, заданной программой и данными от других программ, в том числе сама операционная система реального режима не защищена от программ пользователя.



Внутреннее представление адреса для защищенного режима


При определении внутреннего представления адреса исходили из необходимости совместимости задания адреса для защищенного и реального режимов, т.е. адрес должен быть 2-х компонентным и задаваться с помощью сегментного компонента и смещения, причем, для задания сегментного компонента должен использоваться сегментный регистр. С учетом этих требований, первый компонент задает не адрес начала сегмента, а номер в таблице, где находится этот адрес. Поэтому регистры DS, CS, ... теперь называются не сегментными регистрами, а селекторами. Адрес начала и смещение 32-битные, поэтому диапазон изменения адреса  232 , что соответствует 4 ГБ памяти. Для вычисления адреса используется следующий алгоритм.

По сегментному регистру определяют таблицу, в которой определен адрес начала сегмента, и номер строки в таблице для определения этого адреса. Таблица может быть глобальной и локальной. Глобальная таблица содержит коды и данные, общие для всех программ, в том числе, для операционной системы. К таким кодам относятся, например, коды для библиотеки графического интерфейса. Локальная таблица содержит коды и данные только для одной программы. Благодаря этому обеспечивается разделение адресного пространства между программами.

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

Полученный начальный адрес складывают с адресом, заданным в качестве смещения в команде. Получают исполнительный адрес[4]



Внешнее представление адресов.


Ниже рассмотрено внешнее представление адреса для защищенного режима. Особенности задания адресов в реальном режиме можно изучить по [1].

Используются следующие способы задания адреса: относительная адресация, базисная адресация, индексная адресация, базисно - индексная адресация, относительная базисно - индексная адресация[5].

Относительная адресация. Адрес задается переменной или записью вида: Переменная ±Смещение. Определяет адрес с помощью смещения заданной переменной относительно начала сегмента, внутри которого она определена. Смещение должно быть задано как константное выражение, т.е выражение, значение которого может быть вычислено на этапе компиляции. Может отсутствовать.

Пример:

X                      DD      3, 2

...

MOV               EAX, [X]

MOV               EBX, [X+4]

Данный способ используется для задания адреса простой переменной или элемента массива с заданным номером.

Базисная адресация. Общий вид адреса: [Регистр + Смещение]. Смещение задается так же, как  для предыдущей адресации. В качестве регистра можно использовать любой 32-битный регистр общего назначения, куда предварительно записывается  адрес начала. Для задания адреса можно использовать операцию OFFSET, которая задается перед переменной и означает определить адрес переменной. Другие способы определения адреса в программе будут рассмотрены ниже. Пример:

MOV EAX, OFFSET X; EAX = &X

MOV EBX, [EAX]; EBX = X

Если в качестве регистра используется регистр ESP или EBP, предполагается, что данные используются из сегмента стека, иначе из сегмента данных, например:

MOV   EBX, [EBP]; Данные из стека

Если необходимо переопределить расположение данных, используется переопределение сегмента, например, пусть данные определены в сегменте данных, для доступа к таким данным через регистр EBP пишут:

MOV   EBX, [DS: EBP];

Способ адресации используется для задания адреса произвольного элемента массива. Регистр задает адрес заданного элемента массива.  Например, для обнуления элемента массива с данными длиной 1 байт, используются команды:




MOV   EAX, OFFSET X;       EAX = &X[0]
ADD    EAX, [I]                      EAX = &X[I]
MOV   BL, 0                           BL = 0
MOV   [EAX], BL;                 X[I] = 0
Индексная адресация. Адрес задается в виде: [Переменная + Регистр * Масштаб + Смещение], где:
переменная определяет имя массива;
регистр, называемый индексным, (может быть любой 32 - битный регистр общего назначения) задает номер используемого элемента массива;
масштаб - позволяет учесть длину элемента массива. Принимает значения: 1, 2, 4, 8 (может отсутствовать, в этом случае считается равным 1);
смещение - как для предыдущих способов  адресации (может отсутствовать).
Пример:
MOV   ESI, I
MOV   EAX, [X+ESI * 4]; EAX =  x[I]
Используется для адресации элементов массива стандартной длины. Наиболее наглядный способ задания элемента одномерного массива, задание аналогично его заданию на языке высокого уровня.
Базисно - индексная адресация. Адрес задается в виде: [Регистр1+ Регистр2 * Масштаб + Смещение], где
Регистр1 - базисный регистр - содержит адрес начала массива (как в базисной адресации);
Регистр2 - индексный регистр (как в индексной адресации)
Масштаб  и смещение - как в индексной адресации
Смещение - как для предыдущей адресации (может отсутствовать).
Используется для адресации 2-х мерных массивов.
Пример будет рассмотрен ниже
Относительная базисно- индексная адресация
- адрес задается в виде :[Переменная + Регистр1+ Регистр2 * Масштаб + Смещение], где компоненты определены  в соответствующих способах адресации. Фактически для вычисления адреса используются 5 компонентов.
Переменная,  Регистр1,  Регистр2,  Масштаб,  Смещение,
при трансляции адрес переменной и смещение заменяются одной константой, т.е. с точки зрения внутреннего представления последние два способа эквивалентны и используются для работы с двух мерным массивом.

Адреса


Рассмотренные выше команды показывают, что главным элементом машинной команды и соответствующей ей команды на ассемблере является адрес. Рассмотрим сначала задание адреса в машинных командах (внутреннее представление адреса), а затем в ассемблер программах (внешнее представление адреса).



Выражения


Выражения строятся из:

·        констант;

·        переменных;

·        знаков арифметических операций (+, -, *, /, MOD[6]);

·        знаков отношений (LT[7]

- меньше, LE – меньше равно, EQ - равно, NE – не равно, GT - больше, GE-больше равно);

·        операций побитовой обработки (AND- логическое умножение, OR- логическое сложение, NOT - инверсия, XOR-сложение по модулю 2);

·        операций сдвига (SHL, SHR);

·        дополнительных операций, например, size – размер данного в байтах; length – коэффициент повторения для первого элемента списка, type – значение определяется типом данного и равно длине данного в байтах;

·        зарезервированных символов и идентификаторов.

В табл. 3.2 представлены зарезервированные символы и слова, а также значения, соответствующие этим словам

Таблица 3.2.  Значения для зарезервированных символов и слов

Слово (символ)

Значение

$

Текущее значение адреса

NOTHING

0

?

0

UNKNOWN

0

BYTE

1

WORD

2

DWORD

4

PWORD

6

FWORD

6

QWORD

8

TBYTE

10

PROC

4

CODEPTR

4

DATAPTR

4

 Правила вычисления выражений:

·        Все выражения вычисляются на этапе трансляции.

·        Если в выражении встречается имя переменной, подставляется ее адрес, например в участке программы

X          DD      5

DD      3

MOV   EAX, [X+4]

в регистр EAX запишется значение, которое находится по адресу X+4,

т.е. число 3.

·        При вычислении значения выражения учитывается только целая часть результата, дробная часть отбрасывается.

·        Вычисления проводятся с точностью до 232, если промежуточный или окончательный результат сложения или вычитания превосходит это значение, он усекается (отбрасываются старшие цифры результата), при этом формируется предупреждение. Если результат умножения выходит за пределы, формируется ошибка, связанная с выходом за пределы.


¬        +ёыш фхышЄхы№ Ёртхэ 0, ЇюЁьшЁєхЄё  ю°шсър л-хыхэшх эр 0¬.
¬        LёЄшэр ш ыюц№ ъюфшЁєхЄё  ёююЄтхЄёЄтхээю 0xFFFFFFFF ш 0.
=шцх яЁхфёЄртыхэ яЁшьхЁ ЇЁруьхэЄр яЁюуЁрььv ё тvЁрцхэш ьш. +яЁхфхышЄх Ёхчєы№ЄрЄ ЄЁрэёы Ўшша тvЁрцхэш  т ърцфюь ёыєўрх ш яЁютхЁ№Єх яюыєўхээvх Ёхчєы№ЄрЄv яю ышёЄшэує яЁюуЁрььv.
ideal
p586
extrn ExitProcess:proc
model flat
dataseg
x ddааааааа 1, 2 dup (1),
ааааааааааа ddаааааааааа 0.53, 53., 1.57
аа ddааааааа 1.E-3
аа ddааааааа 5.3E27
аа dbааааааа 5.3E27
аа dwаааааа 5.3E27
аа dqааааааа 5.3E27
аа dtаааааааа 5.3E27
codeseg
begin:
a =аааааааа 1 SHRа 5
b =аааааааа -1 SHL 5
c =а -1 SHL 3
movааааааа eax, size x
movааааааа ebx, length x
movааааааа edx, [ecx+4]
movааааааа esi,7/4
movааааааа edi, 0ffffffffh+2
movааааааа edi, 0ffffffffh/0ffffffffh
movааааааа edi, 1/0
movааааааа edi, 1 lt 0
movааааааа edi, 1 gt 0
callаааааааа ExitProcess

Классификация операторов


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

Формат оператора:

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

обозначения констант;

константы;

регистры;

адреса памяти.

Пример директивы:

MODEL      FLAT

Здесь MODEL - код операции, FLAT - операнд.

Пример команды:

M1:   MOV          EAX, 5; Это комментарий

В этой команде M1 – метка, MOV – код операции, EAX, 5 – операнды.

Директивы и команды изучим постепенно. Сейчас рассмотрим директивы определения констант и выделения памяти.



Директива EQU


Директива EQU позволяет задать любую последовательность символов, которая подставляется в программу вместо имени.

Общий вид директивы EQU:

Имя     EQU    Символы

Значение, заданное для имени (поле символов), не может быть переопределено в программе.

Примеры использования директив:

MaxSize           EQU    100

MOV   EAX, MaxSize+2

Заметим, что команда

MOV   EAX, MaxSize+2

эквивалентна команде

MOV   EAX, 100+2

В поле символов могут быть заданы имена, которые определены в другой директиве EQU. Если имя определено в директиве EQU после использования, требуется транслировать программу, используя несколько просмотров (проходов). Количество проходов при компиляции определяется ключом /mчисло просмотров, например, для трансляции фрагмента программы

A         EQU    B

B         EQU    C

C         EQU    3

MOV   EAX, A

требуется 2 просмотра, т.е. в командной строке необходимо задать ключ /m2



Директива =


Общий вид директивы =:

Имя = выражение

Значение, заданное для имени, вычисляется на этапе трансляции. Может изменяться в программе. В выражении правой части можно использовать переменные, определенные с помощью =, в том числе, и предыдущее значение переменной левой части. Правила задания выражений в директиве = определены выше (подраздел 3.5).

Примеры команд с константами:

X = 5

MaxSize = 100

MOV   EAX, X

MaxSize = MaxSize - 10

MOV   EBX, MaxSize+2

Заметим, что последняя команда эквивалентна команде:

MOV   EBX, 92.

Для четкого понимания различия между директивами определения констант еще раз сравните примеры использования этих директив!



Директивы для определения констант


Для обозначения констант используется 2 типа директив EQU и =.



Директивы выделения памяти


Код директивы определяет тип данного, для которого выделяется память.

В табл. 4.1 определены директивы для выделения памяти и соответствующие типы данных для языка С.

Таблица 4.1. Директивы выделения памяти

Код директивы

Размер данного (байт)

Тип соответствующего данного в языке С

DB

1

char

DW

2

short

DD

4

int, unsigned, float

DF, DP

6

-

DQ

8

double, int64

DT

10

long double

При выделении памяти допускается инициализировать выделенную область и не инициализировать ее. Для правильной работы программы первая переменная в сегменте данных DATASEG должна быть инициализирована. Если переменная инициализируется, в поле операнда задается ее значение, если не инициализируется, ставится знак ? вместо соответствующего значения. Одной переменной может соответствовать несколько значений (массив). Среди элементов массива могут быть инициализированные и неинициализированные элементы. Под адрес всегда отводится 4 байта (данное типа DD). Для инициализации адреса достаточно в поле операнда задать имя данного, для которого определяется адрес.

Примеры директив.

Оттранслировать директивы с языка С++ на ассемблер, если память выделяется в сегменте данных:

char     c1 = ‘a’, c2, c3 = «bcde»;

short    s1 = 2, s2[] = {1,2,3};

unsigned short s2 = 60000;

int        i1[]={7, 3, -2, 4};

float     f1 = 5, f2;

double d1 = 5, d2, d3 = 7e-20;

long     double ld1 = 3, ld2[5];

int *p = i1, *q;

DATASEG

;char c1 = ‘a’, c2, c3 = «bcd»;

c1        db        ‘a’

c2        db        ?

c3        db        «bcd», 0

;short   s1 = 2, s2[] = {1,2,3};

s1         dw       2

s2         dw       1, 2, 3

;unsigned short s3 = 60000;

s3         dw       60000

; int      i1[]={7, 3, -2, 4};

i1         dd        7, 3, -2, 4

;float    f1 = 5, f2;

f1         dd        5.;

f2         dd        ?

;double d1 = 5, d2, d3 = 7e-20;

d1        dq        5.;

d2        dq        ?

d3        dq        7e-20

; long   double ld1 = 3, ld2[5];

ld1       dt         3.;

ld2       dt         5 dup (?)

;int *p = i1, *q;

 p         dd        i1

q          dd        ?

Обратите внимание на задание адресов!

Заметим, что директивы для выделения памяти под c1 .. d3 лучше поменять местами, так как в этом случае доступ к переменным выполняется быстрее с учетом выравнивания адресов.



Классификация команд


По выполняемым действиям команды делятся на:

команды пересылки и обмена;

арифметические команды;

команды для организации разветвлений и циклов и для обработки массивов;

команды для работы с битами;

команды для работы с функциями;

команды управления процессором и определения информации о нем;

команды для работы с FPU;

команды для работы с MMX,



Команда MOV


Общий вид команды:

[Метка:]        MOV   Операнд1, Операнд2 [; Комментарий]

Требования к операндам:

длины операндов должны быть одинаковы и равны (1, 2, 4) байтов;

в качестве операндов можно использовать регистры всех типов (использование управляющих и отладочных регистров не рассматривается в этом курсе), адреса памяти и непосредственные данные (для исходных данных). Разрешенные и неразрешенные типы операндов команды MOV представлены в табл. 4.2

Таблица 4.2. Разрешенные и неразрешенные операнды команды MOV

2 операнд

1 операнд

РОН

СР

Адрес памяти

Непо-средственное данное

РОН

+

+

+

+

СР

+

-

+

-

Адрес памяти

+

+

-

+

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

Примеры команд:

Реализовать на ассемблере операторы присваивания для переменных и массивов, определенных выше.

c2 = c1; c3[1] = c1;

swap (s2[0], s2[2]); s2[1] = s3;

i1[3] = i1[0]

i1[1] = i1[2];

f2 = f1;

d2 = d1;

q = &i1[3]; *p = *q;

Реализация на ассемблере


;c2 = c1; c3[1] = c1;

MOV   AL,[ c1]

MOV   [c2], AL

MOV   [c3+1], AL

;swap (s2[0],s2[2]); s2[1] = s3;

MOV   AX, [s2+4]

MOV   BX, [s2]

MOV   [s2+4], BX

MOV   [s2], AX

MOV   AX, [s3]

MOV   [s2+2], AX

;i1[3] = i1[0]; i1[1] = i1[2];

MOV   EAX, [i1]

MOV   [i1+12], EAX

MOV   EAX, [i1+8]

MOV   [i1+4], EAX

;f2 = f1;

MOV   EAX, [f1]

MOV   [f2], EAX

;d2 = d1;

MOV   EAX, [d1]

MOV   [d2], EAX

MOV   EAX, [d1+4]

MOV   [d2+4], EAX



Использование данных разной длины


Пусть необходимо оттранслировать операторы присваивания:

с2 = s3;

i1[0] = c2;

Для обращения к данным разной длины необходимо преобразование их типов. Для преобразования типов используется преобразователь вида:

<Тип> PTR, где тип : BYTE PTR, WORD PTR, DWORD PTR, PWORD PTR, FWORD PTR, QWORD PTR, TBYTE PTR. Для режима IDEAL вместо записи вида <Тип> PTR можно использовать только тип, например BYTE. Преобразование типа можно использовать только для уменьшения длины. Использование преобразователя типа для увеличения длины приведет к неправильному обращению к данному, т.к. фактически используются последовательные области памяти.

Пример использования преобразования типа в командах пересылки:


; с2 = s3;

mov     AL, [BYTE PTR s3]

mov     [c2], AL

; i1[0] = c2;

mov     eax, 0

mov     al, [c2]

mov     [i1], eax

Реализация последнего оператора корректна только в том случае, если правая часть - число положительное. Для отрицательного числа необходимо другое начальное значение eax.

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

MOVZX – расширение беззнакового числа;

MOVSX – расширение знакового числа.

В этих командах первый операнд - регистр R16 или R32. Второй операнд регистр или память длиной 8, 16 байтов. Дополнение выполняется нулями (MOVZX), или знаковым разрядом (MOVSX).

С помощью этих команд

; i1[0] = c2;

movzx              eax, [c2]

mov                 [i1], eax



Команды обмена


Для обмена данными можно использовать 4 команды MOV или специальные команды.

Команда XCHG

XCHG  R8|R16|R32, R8|R16|R32|M8|M16|M32

Запись означает, что в качестве первого операнда можно использовать регистры общего назначения длиной байт (R8), слово (R16) или двойное слово (R32), в качестве второго – те же регистры или адреса памяти длиной байт, слово или двойное слово (M8, M16, M32).

Пример 1. Используя команду XCHG, реализовать оператор  swap (s2[0], s2[2]) для данных длиной 2 байта


;swap (s2[0], s2[2])

mov     ax, [s2]

xchg     ax, [s2+4]

mov     [s2], ax

Команда позволяет вместо двух общих регистров использовать только один,  вместо 4-х команд используются 3, но команда xchg не входит в основную группу команд, которая оптимизирована и не может быть спарена с другими командами, поэтому использование команд MOV

по времени выполнения более эффективно.

Пример 2. Пусть необходимо поменять местами байты числа длиной 4 байта

X          dd        11223344h

Y          dd        ?

mov     eax, [X]

mov     [Y], eax

mov     al, [BYTE Y]

mov     ah, [BYTE Y +3]

mov     [BYTE Y +3], al

mov     [BYTE Y ], ah

mov     al, [BYTE Y+1]

mov     ah, [BYTE Y +2]

mov     [BYTE Y +2], al

mov     [BYTE Y+1], ah

Команда BSWAP

BSWAP R32

Решение предыдущей задачи с помощью команды BSWAP

MOV               EAX, [X]

BSWAP           EAX

MOV               [Y], EAX



Команды пересылки для работы с адресами


Так как адреса - это целые длиной 32 бита (модель flat), для работы с адресами можно использовать обычную команду mov.

Пример. Реализовать операторы

Int i1[4], *p=i1, *q;

q = &i1[3]; *p = *q;

i1         dd        4 dup (?)

p          dd        i1

q          dd        ?

; q = &i1[3]; *p = *q;

mov     eax, [p];          &i1[0]

add      eax, 3*4;         &i1[3]

mov     [q],eax; q=&i1[3]

mov     ebx, [eax];       *q

mov     ecx, [p]

mov     [ecx], ebx;       *p = *q;

В этом примере используются области памяти для хранения адресов. Для формирования адреса без использования областей памяти можно использовать операцию offset и команду mov.

Например, команда

mov     eax, offset x

записывает адрес переменной x

в регистр eax.

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

Для этого используется специальная команда:

lea       <оп1>, <оп2>.

В качестве <оп1>

используется регистр 32-битный или 16 битный в зависимости от типа приложения. В качестве <оп2>

используется адрес памяти, для задания которого можно применять любой способ адресации.

Пример 1. Команды

mov     eax, offset x

lea       eax,  [x]

эквивалентны по выполняемым действиям, но первая команда более эффективна.

Пример 2. Записать адрес третьего элемента массива x , если длина элемента – 4 байта:

Lea      eax, [x+12]



Использование команды lea для арифметических вычислений


Команду lea можно использовать для умножения на некоторые целочисленные константы, это позволяет уменьшить требуемое время выполнения операции, т.к. команда умножения требует больше времени, чем команда lea.

Примеры

lea       eax, [eax+eax];                      eax = eax *2

lea       eax, [eax+eax*2];                  eax = eax *3

lea       eax, [eax*4];                           eax = eax *4

lea       eax, [eax+eax*4];                  eax = eax *5

lea       eax, [eax+eax*8];                  eax = eax *9

Напоминаем, что масштабный множитель можно задавать только для одного регистра. Значение масштабного множителя {1,2,4,8}.



Использование команд mov


Пример. Записать в стек число 5.

При записи в стек должны выполняться действия:

esp--;

*esp =5;

sub       esp, 4

mov     [dword esp], 5

Преобразование типа требуется, так как при косвенной адресации неизвестен тип данного.



Использование специальных команд


Используются команды:

push  <Оп>-        Запись в стек <Оп>. В качестве операнда могут быть регистры общего назначения с длиной, равной длине элемента стека, константа или адрес памяти. Команда выполняет действия: ESP--; *ESP = Оп;  

pop    <Оп>-        Извлечение из стека. В качестве операнда могут быть регистры общего назначения с длиной, равной длине элемента стека или адрес памяти. Команда выполняет действия: Оп = *ESP++

pushad        -        Запись в стек содержимого всех общих регистров

popad         -        Извлечение из стека содержимого всех общих регистров.

Пример. Поменять местами значения x, y.

Push    [x]

push     [y]

pop      [x]

pop      [y]



Особенности команд пересылки для работы со стеком


Длина элемента стека зависит от типа приложения. Для 32-битного приложения длина равна 4 байта, для 16-битного -2 байта. Элемент стека выравнивается на границу длины.

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

обычные команды mov и специальные команды.



Классификация арифметических команд


По типу используемых данных арифметические команды делятся на команды для работы:

с целыми числами;

с числами с плавающей точкой.

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

По длине используемых данных команды для целых чисел делятся на:

команды для работы с двойными словами (4 байта), словами (2 байта), байтами.

Ниже будут рассмотрены команды для работы с целыми двоичными числами.



Правила использования арифметических команд


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

2.    Длины операндов должны совпадать, длина результата равна длине операндов.

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

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



Основные арифметические команды


Основные арифметические команды представлены в табл. 5.1

Таблица 5.1. Основные арифметические команды

Назначение

Общий вид

Комментарий

Сложение

add  <Оп1>, <Оп2>

xadd <Оп1>, <Оп2>

Оп1 = Оп1 + Оп2

Оп1«Оп2, Оп1 = Оп1 + Оп2

Вычитание

sub <Оп1>, <Оп2>

Оп1 = Оп1 - Оп2

Умножение беззнаковое

mul  < Оп >

Умножение знаковое

imul < Оп >

Умножение знаковое 2-х операндное

imul <Оп1>, <Оп2>

Оп1 = Оп1 * Оп2

Умножение знаковое 3-х операндное

imul <Оп1>, <Оп2>, константа

Оп1 = Оп2 * константа

Деление беззнаковое

Деление знаковое

div  <Оп>

idiv <Оп>

Во всех командах не допускается два адреса памяти.

При выполнении сложения с помощью команды xadd второй операнд должен быть регистром.

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

Деление выполняется нацело, дробная часть результата отбрасывается. Одновременно с частным формируется остаток от деления. Если при выполнении деления частное не помещается в отведенное для него поле, возникает особая ситуация “Деление на ноль”. Команда деления может требовать специальной подготовки для записи делимого. Подготовка состоит в расширении делимого знаковым разрядом для чисел со знаком и обнулении старшей части делимого для чисел без знака. Для этих целей можно использовать команды пересылки или специальные команды. Команды пересылки (MOV) рассмотрены выше



Специальные команды


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

Таблица 5.2   Команды для подготовки деления

Выполняемое преобразование

Код

Исходное данное

Результат

Байт в слово

CBW

AL

AX

Слово в двойное слово

CWD

AX

DX, AX

двойное слово в два двойных слова

CDQ

EAX

EDX, EAX

Слово в двойное слово

CWDE

AX

EAX

Пример использования арифметических команд.

Пример 5.1 Пусть заданы значения X, Y. Выполнить операции +, - , *, /  и вычисления остатка для данных типа DWORD

ideal

p586

model   flat

extrn ExitProcess:proc

dataseg

x       dd      7fffffffh

y       dd      7fffffffh

z       dd      ?

zl      dd      ?

zh      dd      ?

codeseg

begin:

; z=x+y

mov     eax, [x]

add     eax, [y]

mov     [z], eax

; z=y+x

mov     eax, [x]

mov     ebx, [y]

xadd     eax, ebx

mov     [z], eax

; z=x-y

mov     eax, [x]

sub     eax, [y]

mov     [z], eax

; z=x*y (x, y - unsigned)

mov     eax, [x]

mul     [y]

mov     [zl], eax

mov     [zh], edx

; z=x*y (x, y -int)

mov     ebx, [x]

imul     ebx, [y]

mov     [zl], ebx

; z=x*5 (1 способ)

mov     ebx, [x]

imul     ebx, 5;

mov     [zl], ebx

; z=x*5 (1 способ)

mov     ebx, [x]

imul     eax, ebx, 5

mov     [zl], eax

;zl=x/y; zh=x%y; x, y - unsigned

mov     eax, [x]

sub     edx, edx

div     [y]

mov     [zl], eax

mov     [zh], edx

;zl=x/y; zh=x%y; x, y -int

mov     eax, [x]

cdq

idiv     [y]

mov     [zl], eax

mov     [zh], edx

call    ExitProcess

end     begin

Для обеспечения максимально быстрого выполнения умножения на целое число используется макрос FASTIMUL, в котором умножение заменяется операциями сложения и сдвига, например, умножение на 5 рассматривается как умножение на 4 (сдвиг на 2 бита влево и сложение первоначального значения). Требуемые операции сдвига и сложения формирует компилятор по сомножителю. Общий вид макрокоманды:

FASTIMUL оп1, оп2, константа; оп2 * константа® оп1        



Организация вычислений с многократной точностью


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

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

Таблица 5.3 Команды для вычислений с многократной точностью

Назначение

Общий вид

Комментарий

Сложение

adc  <Оп1>, <Оп2>

Оп1 = Оп1 + Оп2 + бит переноса

Вычитание

sbb <Оп1>, <Оп2>

Оп1 = Оп1 - Оп2- бит переноса

При переполнении (заеме) формируется бит переноса, который учитывается при выполнении операций сложения и вычитания для очередной порции числа (цифры).

Рассмотрим примеры вычислений с многократной точностью.

 Пример1. Составить программу для сложения двух 64 - разрядных чисел[8]

dataseg

x          dd        12345678h, 9abcdefah

y          dd        0ffffffffh, 0ffffffffh

z           dd        3 dup (?)

...

Codeseg

..

; Сложение младших «цифр» числа.

mov     eax, [x]

add      eax, [y]

mov     [z], eax

; Сложение старших «цифр» числа.

mov     eax, [x+4]

adc      eax, [y+4]

mov     [z+4], eax

;Учет переноса

mov     [z+8], 0

adc      [z+8], 0

Очевидно, что для вычитания таких чисел необходимо команы ADD, ADC заменить командами SUB, SBB.

Пример 2. Составить программу для умножения двух 64-разрядный чисел x, y.

Очевидно, что 

x = x1* 232 + x0;

y = y1* 232 + y0;

где (x1, x0) – старшая и младшая цифры числа x;

где (y1, y0) – старшая и младшая цифры числа y;

В результате умножения x * y получим:

x * y = (x1* 232

+ x0) * (y1* 232 + y0) = x0 * y0 + (x1 * y0 + x0 *y1) 232 + x1 * y1.
¦ЁюуЁрььр, яЁштхфхээр  эшцх, тvўшёы хЄ чэрўхэшх лЎшЇЁ¬ яЁюшчтхфхэш  ё єўхЄюь яхЁхяюыэхэш  эр ърцфюь °рух.
dataseg
xааааааааа ddааааааа 12345678h, 9abcdefah
yааааааааа ddааааааа 0ffffffffh, 0ffffffffh
zаааааааааа ddааааааа 4 dup (?)
...
Codeseg
...
subаааааа eax, eax
movаааа [z+8], eax
movаааа [z+12], eax
; ¦ырф°р  ЎшЇЁр Ёхчєы№ЄрЄр z0
movаааа eax, [x]
mulааааа [y]
movаааа [z], eax
movаааа z[4], edx
; ¦эрўхэшх z1
movаааа eax, [x+4]
mulааааа [y]
addааааа [z+4], eax
adcааааа [z+8], edx
movаааа eax, [x]
mulааааа [y+4]
addааааа [z+4], eax
adcааааа [z+8], edx
adcааааа [z+12], 0
; ¦эрўхэш  z2 ш z3
movаааа eax, [x+4]
mulааааа [y+4]
addааааа [z+8], eax
adcааааа [z+12], edx
¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы  фхыхэш  64 сшЄэюую ўшёыр эр 32-сшЄэюх.
xааааааааа ddааааааа ..., Е
yааааааааа ddааааааа Е
zаааааааааа ddааааааа ?, ?
movаааа edx, 0
movаааа eax, [x+4]
divаааааа [y]
movаааа [z+4], eax
movаааа eax, [x]
divаааааа [y]
movаааа [z], eax
¦ЁхфюёЄхЁхурхь Tрё юЄ эхтхЁэюую Ёх°хэш :
movаааа edx, 0
movаааа edx, [x+4]
movаааа eax, [x]
divаааааа [y]
movаааа [z], eax
¦Єю Ёх°хэшх ьюцхЄ яЁштхёЄш ъ ю°шсъх л-хыхэшх эр 0¬, хёыш ўрёЄэюх эх яюьх•рхЄё  т ЁхушёЄЁ EAX, эряЁшьхЁ, хёыш x>232, р y = 1!

Дополнительные арифметические команды


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

Таблица 5.4. Дополнительные арифметические команды

Назначение

Общий вид

Комментарий

Увеличение на 1

inc  <Оп>

Оп++

Уменьшение на 1

dec <Оп>

Оп--

Инвертирование знака

neg <Оп>

Оп = - Оп



Команды безусловного перехода


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

Команды косвенного перехода в качестве операнда принимают адрес с адресом перехода.

В командах короткого перехода для задания смещения, соответствующего метке, используется один байт, в противном случае -четыре.

Общий вид команд безусловного перехода:

JMP     [SHORT] операнд

Примеры использования команд перехода:

ideal

p586

model   flat

extrn ExitProcess:proc

dataseg

pm1     dd      m4, m5, m6

codeseg

begin:

; Короткий переход. Команда занимает 2 байта

jmp    short m1

m1:

; Обычный переход. Команда занимает 5 байтов

jmp    m2

m2:

m3:

; Использование косвенного перехода.

; Переход на метку m4, m5,  m6 или m7 в зависимости от содержимого

; регистра  EAX  

        mov     eax, 1; goto m4

        jmp     [pm1+eax*4]

m4:     mov     ebx, 4

        jmp     short m7

m5:     mov     ebx, 5

        jmp     short m7

m6:     mov     ebx, 6

        jmp     short m7

m7:

call    ExitProcess

end     begin

Заметим, что в случае ссылки назад, когда метка находится выше, чем команда перехода, слово short

можно не писать, тип перехода определит компилятор.



Коды условий


Арифметические команды формируют коды условий в зависимости от результата выполнения команды:

Перенос (c) – если есть  перенос за границы разрядной сетки ячейки;

Четность  (p) – количество битов младшего байта результата – четное;

Знак (s) – результат отрицательный;

Нуль (z)– результат равен нулю;

Переполнение (o).

Признак результата записывается в регистр флагов. Регистр флагов – 32 битное число, в котором каждый флаг записывается в фиксированный бит. Для рассмотренных выше флагов используются биты:

C –бит 0

P – бит 2

Z – бит 6

S – бит  7

O – бит 11 .

 Содержимое регистра флагов может быть записано в стек (команда pushfd)  и прочитано из стека (например, команда pop eax).

Пример. Записать содержимое регистра флагов в регистр EAX:

Pushfd

Pop      eax

Команды пересылки не изменяют содержимое регистра флагов!

Специально для изменения битов регистра флагов используются команды:

Команда сравнения : cmp оп1, оп2. Операнды задаются как для арифметических- команд. Фактически выполняется команда вычитания, но результат не записывается вместо первого данного, а только формируется регистр флагов.

Команда test оп1, оп2. Операнды задаются как для арифметических- команд. Фактически выполняется операция поразрядного умножения, но результат  не записывается в оп1, а флаги формируются. Команда используется для сравнения с нулем всего числа или отдельных его битов.

Команда bt оп, константа. Команда проверяет содержимое бита, номер которого задан константой, в операнде. Результат записывается в бит переноса регистра флагов.

 



Команды условного перехода


Общий вид команды:

[Метка]         Код     метка

Косвенный условный переход не используется. При трансляции команды вместо метки записывается разность между адресом метки и адресом очередной после команды условного перехода команды. Если эта разность помещается в один байт (-128.. 127) команда называется короткой (short). Для команд с ссылкой назад компилятор определяет тип команды сам. Для ссылок назад тип задается программистом, если не задан, принимается обычным. Если программист задал короткий переход, а он невозможен, компилятор выводит сообщение:  RELATIVE JUMP OUT OF RANGE 

Команды перехода делятся на переходы по флагам, переходы для знаковых и беззнаковых данных.

Таблица 6.1 Команды условного перехода

Переходы по флагам

Переходы для знаковых данных

Переходы для беззнаковых данных

Прямой

Обратный

Прямой

Обратный

Прямой

Обратный

Jc

jnc

Jl (<)

Jnl (>=)

Jb(<)

Jnb(>=)

Jp

jnp

Jle (<=)

Jnle(>)

Jbe(<=)

Jnbe(>)

Jz

jnz

Je (==)

Jne(!=)

Je(==)

Jne(!=)

Js

jns

Jg (>)

Jng (<=)

Ja (>)

Jna(<=)

Jo

jno

Jge(>=)

Jnge (<)

jae(>=)

jnae(<)

Примеры использования команд.

1.       Составить программу для вычисления максимального из двух заданных чисел длиной 32 бита (числа со знаком).

Ideal

P586

Model  flat

Extrn ExitProcess:proc

Dataseg

X          dd        6

Y          dd        7

Z          dd        ?

Codeseg

Begin: mov      eax, [X]

Cmp    eax, [Y]

Jge       short m1

Mov     eax, [Y]

M1:

Mov     [Z], eax

End      begin

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

X          dd        0fefefefeh, 0ffffffffh

Y          dd        0ffffffffffh, 0ffffffffh

Z          dd        ?,?

;Пусть сначала заданы младшая, а затем старшая цифра.

Mov     eax, [X+4]

Cmp    eax, [Y+4]

Ja        mx

Jb        my

Mov     eax, [X]
Cmp    eax, [Y]
Jae       mx
My:
Mov     eax, [Y]
Mov     [Z], eax
Mov     eax, [Y+4]
Mov     [Z+4], eax
Jmp     short m1
Mx:
Mov     eax, [X]
Mov     [X], eax
Mov     eax, [X+4]
Mov     [X+4], eax
M1:
Пример 3.
Скопировать данные из одной области памяти в другую, если заданы адреса обеих областей и размер
Addr1  dd        …
Addr2  dd        …
Len      dd        …
Способ 1
Mov     eax, [addr1]
Mov     edx, eax
Add      edx, [len]
Mov     ebx, [addr2]
For1:
Mov     cl, [eax]
Mov     [ebx], cl
Inc       eax
Inc       ebx
Cmp    eax, edx
Jle        for1

Ошибка!:  команду Jle необходимо заменить командой для сравнения чисел без знака! (Jbe).
Недостаток решения! Не учтено возможное перекрытие областей!
Способ 2
Ab = addr1; h = 1; Be = addr2;
If (addr2 != addr1){
   If (addr2 > addr1 && addr2 < addr1+len){
     Ab +=  len – 1; Bb +=len – 1;
     H=-1;
 }
  for (i=0; i<len; i++){
   *Bb = *Ab
   bb+=h;
   aa+=h;
  }
;Ab = addr1; h = 1; Be = addr2;
mov     eax, [addr1]
mov     ebx, [addr2]
mov     edx, 1
;If (addr2 != addr1){
cmp     eax, ebx
je         short break
;    If (addr2 > addr1 && addr2 < addr1+len){
cmp     eax, ebx
jbe       m1
mov     ecx, eax
add      ecx, [len]
cmp     ebx, ecx
jae       m1
;Ab +=  len – 1; Bb += len – 1;      H=-1;
mov     eax, ecx
dec       eax
add      ebx, [len]
dec       ebx
mov     edx, -1
 ;}
  ;for (i=0; i<len; i++){ *Bb = *Ab;    bb+=h;    aa+=h;
mov     esi, [len]
for2:
mov     cl, [eax]
mov     [ebx], cl
add      eax, edx
add      ebx, edx
sub       esi, 1
jnz        for2

Специальные команды


Команда loop:

Loop    <метка>

Выполняемые действия:

Ecx--; if (ecx) goto <метка>

Структура циклического участка программы:

; Подготовка цикла (формирование начальных значений параметров, счетчика)

<метка>:

; Циклический участок программы

 Loop   <метка>

Пример 1. Составить программу для вычисления суммы 1+2+3 +… + 100[9]

For (i=1, s=0; i<=100; i++) s+=i;

Mov     eax, 0; s=0

Mov     ecx, 100

For1:

Add      eax, ecx

Loop    for1

Команды Loopz, Loopnz дополнительно с условием ecx = 0 проверяют флаг нуля. Если флаг имеет заданное значение – то остаемся в цикле.

Команда jecxz – команда условного перехода при ecx, равном нулю – защита от “зацикливания”

Заметим, что использование команды Loop менее эффективно, чем простых команд: dec, jnz



Организация вложенных циклов


Пусть необходимо реализовать участок программы:

For      (i=0; i<n; i++)

   For (j=0; j<m; j++)

      …

Можно для параметров цикла использовать разные регистры – но регистров мало. В этом случае, если для параметров достаточно 16 битных значений можно использовать один регистр для двух параметров.

Пример 1. Пусть необходимо реализовать участок программы

For      (i=0; i<5; i++)

   For (j=0; j<6; j++)

      …

; For    (i=0; i<5; i++)

mov     ecx, 5

shl        ecx, 16; Счетчик в старшую часть регистра

for1:

;   For (j=0; j<6; j++)

mov     cx, 6

for2:

  …

dec       cx

jnz        for2

sub       ecx, 10000h

jnz        for1

Пример 3. Составить программу для вычисления произведений всех элементов вида a[0] * b[0], a[0] * b[1], … a[0] * b[m-1]  ,…a[n-1] * b[m-1]:

P=1;

For (i=0; i<n; i++){

      For (j=0; j<m; j++)    P=p*a[i]*b[j];

}

; P=1;

mov     eax, 1

;For (i=0; i<n; i++){

mov     ecx, [n]

shl        ecx, 16

mov     ebx, offset a

;      For (j=0; j<m; j++)  

fori:

mov     cx, [m]

mov     esi, offset b

;  P=p*a[i]*b[j];

forj:

   mul   [dword ptr ebx]

   mul   [dword ptr esi]

;}

   add   esi, 4

   dec    cx

   jnz     forj

   add  ebx, 4

   dec    ecx, 4

   jnz     fori



Монотонное изменение индекса


Индекс изменяется монотонно, если он увеличивается или уменьшается с постоянным шагом.

Для адресации элементов массива используются способы адресации [см. 3.4.3]:

<Имя массива + смещение> - для адресации заданного элемента массива - относительная адресация. Смещение - константное выражение;

[Регистр] - регистр содержит адрес текущего элемента массива - базисная адресация. Пусть индекс массива изменяется в пределах [p:q]. В этом случае адрес i-ого элемента массива равен адресу начала массива + (i-p)* l, где l - размер одного элемента массива. Заметим, что формула упрощается при p равном нулю. В дальнейшем будем предполагать, что минимальное значение индекса всегда равно 0.

[Регистр + смещение] (базисная адресация)- Смещение - константное выражение - используется, если в программе одновременно используются i-ый и i+...-ый элемент массива;

[Имя массива + регистр * масштаб] (индексная адресация)- регистр задает значение индекса в массиве - используется при работе с массивами, размер элементов которых равен масштабному множителю. 

Пример 1. Составить программу для вычисления

.

Вариант 1. Использование базисной адресации

For (s=i=0; i<n; i++) s+=x[i];

;For (s=i=0; i<n; i++)

sub       eax, eax;          s

mov     ecx, [n];          i

mov     ebx, offset x

; s+=x[i];

fori:

add      eax, [ebx]

; i++

add      ebx, 4

; i<n

loop     fori      

mov     [s], eax

Вариант 2. Индексная адресация

;For (s=i=0; i<n; i++)

sub       eax, eax, s=0

sub       ebx, ebx;          i=0

mov     ecx, [n]

for1:

add      eax, [x+ebx*4]

inc       ebx

loop     for1     

mov     [s], eax

Вариант 3. Так как суммирование можно выполнять, начиная с последнего элемента, регистр ecx можно использовать в качестве индексного регистра

sub       eax, eax

mov     ecx, [n]

for1:

add      eax, [x+ ecx*4-4]

loop     for1

mov     [s], eax

Сравнение программ показывает, что 3 вариант наиболее выгодный с точки зрения числа команд.


¦ЁшьхЁ 2.
TюёЄртшЄ№ яЁюуЁрььє фы  тvўшёыхэш  ёєьь:
y0 = x0 + xn-1;
y1 = x1 + xn-2;
...
yn/2 = xn/2 + xn/2+1;
for (i=0, j=n-1; i<n/2; i++, j--){
аа y[i] = x[i] + x[j];
}
movаааа ecx, [n]
movаааа edx, ecx
shrаааааа ecx, 1
jecxzааа m1
movаааа eax, 0
for1:
movаааа ebx, [x+eax*4]; x[i]
addааааа ebx, [x+ecx*4-4]
movаааа [y+eax*4], ebx
incаааааа eax
decаааааа edx
loopаааа for1
m1:
а ¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы  эрїюцфхэш  ьръёшьры№эюую ўшёыр ш хую эюьхЁр т ьрёёштх ўшёхы фышэющ 32 сшЄр.
For (max = x[nom = 0], i=1; i<n; i++ )
аа if (x[i] >max){
ааааа max = x[i]; nom = i;
аа }
movаааа eax, 0; nom
movаааа ebx, [x]; max
movаааа ecx, [n]
decаааааа ecx
jecxzааа m1
movаааа edx, 1; i
for3:
movаааа esi, [x+edx*4]
cmpаааа esi, ebx
jleааааааа short next
movаааа ebx, esi
movаааа eax, edx
next:
incаааааа edx
loopаааа for3
movаааа [max], ebx
movаааа [nom], eax
-ы  Єюую ўЄюсv ЁхушёЄЁ ECX шёяюы№чютрЄ№ фы  рфЁхёрЎшш фрээvї шчьхэшь яЁюуЁрььє Єръшь юсЁрчюь, ўЄюсv яюшёъ эрўшэрыё  ё яюёыхфэхую ўшёыр
for (i=n-2, max = x[nom = n-1]; i>=0; i-- ){
аа if (x[i]>max) {max= x[i]; nom = i;}
}
movаааа ecx, [n]
decаааааа ecx
jecxzааа m1
movаааа eax, ecx
movаааа ebx, [x+ecx*4]
for4:
movаааа esi, [x+ecx * 4 - 4]
cmpаааа esi, ebx
jleааааааа short next1
movаааа eax, ecx
movаааа ebx, esi
loopаааа for4
movаааа [max], ebx
movаааа [nom], eax
¦рёёьюЄЁшь яЁшьхЁ ЁрсюЄv ёю ёЄЁюърьш.
¦ЁшьхЁ 5. TюёЄртшЄ№ яЁюуЁрььє фы  юс·хфшэхэш  фтєї ёЄЁюъ ё эєыхтvь чртхЁ°шЄхыхь т юфэє ёЄЁюъє.
For (i=0; str1[i]; i++) str3[i] = str1[i];
for (j=0; str3[i+j=str2[j]; j++);
; For (i=0; str1[i]; i++) str3[i] = str1[i];
movаааа eax, 0; i=0
for6:
movаааа bl, [str1+eax]
testаааааа bl, bl
jeаааааааа next5
movаааа [str3+eax], bl
incаааааа eax
jmpааааа for6
next5:
movаааа ecx, 0
for7:
movаааа bl, [str2+ecx]
movаааа [str3+ecx+eax], bl
testаааааа bl, bl
jeаааааааа short mmm
incаааааа ecx
jmp for7
mmm: