Основные определения
Системное программное обеспечение (СПО)– комплекс программ для увеличения производительности вычислительной системы и пользователя. Примером СПО является операционная система. Компонентом СПО является системная программа.
Цель курса.
· Изучить языки для написания системных программ.
· Изучить приемы и методы создания системных программ
Характеристика языков системного программирования
Язык системного программирования должен удовлетворять следующим свойствам:
· должен обеспечить создание эффективной программы по требуемым ресурсам (памяти, времени процессора, дисковое пространство)
· обеспечить возможность использования всех особенностей компьютера, например доступ ко всей оперативной памяти, работу в защищенном режиме, использование и управление кэш - памятью и т.д.
· некоторые программы, например драйверы для управления внешними ресурсами имеют специальную структуру. Язык должен обеспечить возможность создания программ произвольной структуры.
· должен быть надежным и наглядным для уменьшения вероятности пропуска ошибок в программе.
В настоящее время нет языка, полностью удовлетворяющего этим свойствам. Всем требованиям, кроме последнего, удовлетворяет машинный язык и близкий к нему язык Ассемблера. Последнему требованию удовлетворяют языки высокого уровня, но они не удовлетворяют первым трем требованиям, поэтому при создании системных программ используют и язык Ассемблер и язык высокого уровня (язык С, С++). И, хотя для систем, поддерживающих работу с процессорами разных типов, например, 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: