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

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


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


Цель курса.


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


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


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


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


Основные команды заданы в табл. 10.1
Таблица 10.1. Основные команды для работы с битами

Но-мер
Назначение
Код
Выполняемые действия
Формируемые флаги
1
Побитовое сложение
OR
оп1|=оп2
c=0, o=0, z, p, s
2
Побитовое умножение
AND
оп1&=оп2
c=0, o=0, z, p, s
3
Проверка
TEST
оп1&оп2
c=0, o=0, z, p, s
4
Побитовое
Отрицание
NOT
~оп1
Флаги не изменяются
5
Сложение по модулю 2
XOR
оп1^=оп2
c=0, o=0, z, p, s

Примеры использования команд.
Пример1. Записать команды для:
установки в 1 заданного бита в байте;
установки в 0 заданного бита в слове;
инвертирования заданного бита в двойном слове;
проверки заданного бита в двойном слове;
Пусть номер бита является константой
BitNumber  EQU  3
b        db      37h
w       dw     1234h
d1      dd      12345678h
d2      dd      12345678h
...
;        установка в 1 заданного бита в байте
OR    b, 1 SHL BitNumber
;        установка в 0 заданного бита в слове
AND  w, NOT (1 SHL BitNumber)
;        инвертирование заданного бита в двойном слове
XOR  d1, 1 SHL BitNumber
проверка заданного бита в двойном слове
TEST d2, 1 SHL BitNumber
jz       zero
...
zero:
Пример 2.
Задан массив байтов. Переписать в другой массив те байты первого массива, в которых биты 0 и 4 единичные, биты 1, 3, 7 - нулевые, значения остальных битов не имеет значения.
Для решения этой задачи сформируем маски для выделения требуемых битов и проверки заданных битов на 1. Первая маска может быть сформирована так:
(1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7).
Вторая маска имеет вид:
(1 shl 0) or (1 shl 4).
Ideal
p386
model flat
extrn ExitProcess:proc
dataseg
a          db        11h, 12h, 23h, 34h, 56h, 78h, 90h, 0a1h, 0b2h, 0c3h, 0d4h, 0e5h

count   dd        11
b          db        11 dup (0)
codeseg
begin:
mov     ecx, [count]
mov     eax, 0; индекс исходного массива
mov     edx, 0   ;индекс результирующего массива
fori:
mov     bl, [a+eax]
mov     bh, bl
and      bl, (1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7)
xor       bl, (1 shl 0) or (1 shl 4)
jnz        short next
mov     [b+edx], bl
inc       edx
next:
inc       eax
loop     fori
call      ExitProcess
end      begin
В этом примере только первое число удовлетворяет поставленным требованиям.
Пример 3. Вычислить значение булевского выражения.
Основные команды для работы с битами

Значение выражения равно истине, если значение хотя бы одного слагаемого равно истине. Для выделения требуемых битов и проверки единичных битов используются константы:
; Первое слагаемое
c11      db        11010110b
c12      db        10000110b
; Второе слагаемое
c21      db        01101100b
c22      db        01001000b
; Третье слагаемое
c31      db        00000111b
c32      db        00000101b
Пусть вычисляется значения для байта
x          db        10101010b
Результат записывается в поле RES и равен 0 для ответа «Ложь» и 1 для ответа «Истина».
RES     db        ?
; Проверка первого слагаемого
mov     RES, 1; Пусть ответ равен «Истина»
mov     al, [x]
and      al, [c11]
xor       al, [c12]
jz          short    true
; Проверка второго слагаемого
mov     al, [x]
and      al, [c21]
xor       al, [c22]
jz          short    true
; Проверка третьего слагаемого
mov     al, [x]
and      al, [c31]
xor       al, [c32]
jz          short    true
mov     [RES], 0
true:

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


Сдвиг может быть обычным и циклическим. При обычном сдвиге разряды, выходящие за разрядную сетку, уничтожаются. Для циклического сдвига они записываются на место освободившихся разрядов.
Обычный сдвиг может быть арифметическим и логическим. При арифметическом сдвиге вправо свободные позиции слева заполняются знаковым (старшим) разрядом. При логическом сдвиге вправо свободные разряды слева заполняются нулями. Арифметический сдвиг вправо используется обычно для деления нацело знакового числа на 2n, а логический - для беззнакового числа. Операции сдвига влево для обоих типов команд выполняются одинаково.
Циклический сдвиг может быть с переносом и без переноса. Схема выполнения команд для обоих типов сдвига на 1 бит вправо представлен на рис. 10.1a и рис. 10.1b.
а - циклический сдвиг с переносом
Классификация команд

b- циклический сдвиг без переноса
Классификация команд

Рис. 10.1 Схема выполнения циклического сдвига
Стрелка вверху задает направление сдвига (вправо). Как видно из рисунка, выдвигаемый бит всегда помещается в бит переноса С. При циклическом сдвиге предыдущее содержимое бита переноса записывается в освободившийся разряд, при обычном – теряется, а в освободившийся разряд записывается выдвигаемый бит.
Заметим, что при любом сдвиге последний из выдвинутых разрядов записывается вместо бита переноса.


Кодировка команд сдвига


Первая буква кода определяет тип сдвига. Буква S соответствует обычному, а буква R циклическому сдвигу. Вторая буква уточняет тип сдвига. Для обычного сдвига используется буква A для арифметического и буква H для логического сдвига. Для циклического сдвига вторая буква С
соответствует сдвигу с переносом и буква O сдвигу без переноса. Третья буква определяет направление сдвига. Буква R
соответствует сдвигу вправо, и буква L сдвигу влево. Таким образом коды команд сдвига:
Кодировка команд сдвига



Общий вид команд сдвига


Общий вид команд сдвига:
код      оп1, оп2
оп1 - сдвигаемое данное. Может быть байтом. Словом или двойным словом. Может быть задано в общем регистре или памяти.
оп2 - задает, на сколько разрядов выполняется сдвиг. Может быть константой или регистром CL, в который предварительно необходимо записать константу сдвига.


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


Пример 1. Установить в 1 бит i
двойного слова x, если значение i вычисляется в программе.
Mov     cl, [i]
mov     1, eax
shl        eax, cl
or         [x], eax
Пример 2. Задан массив чисел, каждое число не более 32. Упаковать этот массив таким образом, чтобы под каждое число отводилось 5 бит.
Ideal
p386
model              flat
extrn                ExitProcess:proc
dataseg
x          db        3,5,7,9,2,4,6
xpack   db        (((xpack-x)*8+4)/5) dup (0)
count   dd        xpack   - x
codeseg
begin:
mov     eax, [count]
; for (i=0; i< count; i++){
   unsigned *p = xpack+(i*5)/8
}
mov     esi, 0;
mov     ebx, 0
fori:
mov     dx, 0
mov     ecx, ebx
and      ecx, 7
mov     dl, [x+esi]
shl        dx, cl
mov     ecx, ebx
shr       ecx, 3
or         [xpack+ecx], dx
add      ebx, 5
inc       esi
cmp     esi, [count]
jl          fori
call ExitProcess
end      begin





Группа команд: проверить и изменить


Команды проверяют значение заданного бита (записывают его содержимое в бит C) и меняют значение в соответствии с заданным кодом. Первый операнд задает проверяемое и изменяемое данное, второй - номер бита. В качестве первого операнда может быть регистр и память размером 1, 2 или  4 байта. Второй операнд задается константой или регистром. Команды для проверки и изменеия заданных битов представлены в табл. 10.2
Таблица 10.2. Команды для проверки и изменеия заданных битов

Назначение
Код
Выполняемые действия
Проверка бита
Bt
C=(oп1 & (1<<оп2))>> оп2
Проверка и установка бита
Bts
C=(oп1 & (1<<оп2))>> оп2;
oп1 |= (1<<оп2))
Проверка и сброс бита
Btr
C=(oп1 & (1<<оп2))>> оп2;
oп1 &= (~(1<<оп2))
Проверка и инвертирование бита
Btc
C=(oп1 & (1<<оп2))>> оп2;
oп1 ^= (1<<оп2))

Примеры использования команд
Пример 1. Вычислить сумму всех битов, стоящих на четных местах в двойном слове
p486
ideal
model  flat
extrn ExitProcess:proc
dataseg
x          dd        0a5a5a5a5h
s           dd        ?
codeseg
begin:
xor       edx, edx; s=0
mov     eax, [x]
mov     ecx, 16
mov     ebx, 0
for:
bt         eax, ebx
adc      edx, 0
add      ebx, 2
loop     for
mov     [s], edx
call      ExitProcess
end begin
Программа выдает ответ 8.


Команды длинного сдвига


Общий вид команды:
Команды длинного сдвига
,
где оп1 - задает, что сдвигается (память или 32 битный регистр);
оп2 - откуда вдвигаются разряды на освободившиеся места (32 - битный регистр);
оп3 - на сколько сдвиг (константа или регистр CL ).
Схема выполнения команды SHLD представлена на рис. 10.2
Команды длинного сдвига

Рис. 10.2
Заметим, что содержимое оп2 сдвигается виртуально, фактическое содержимое этого регистра не изменяется.
Пример использования команды.
Задано число длиной 1024 бита. Разделить его на 8.
Для деления числа на 8 достаточно сдвинуть его на 3 бита вправо, в этом случае получим частное т деления. Остаток равен младшим трем битам исходного числа.
p486
ideal
model  flat
y=x>>3; ost = x[0]&7
extrn ExitProcess:proc
dataseg
x          dd        32 dup (0ffffffffh)
y          dd        32 dup (?); Частное
ost        dd        ?         ; Остаток
codeseg
begin:
; y=x
mov     ecx, 32
for1:
mov     eax, [x+ecx*4-4]
mov     [y+ecx*4-4], eax
loop     for1
;for (i=0; i<31; i++)
mov     ecx, 31
mov     eax, 0
; shrd (y[i], y[i+1], 3);
for2:
mov     ebx, [y+eax*4+4]
shrd     [y+eax*4], ebx, 3
;i++
inc       eax
loop     for2
;shr (y[i], 3);
shr       [y+eax*4], 3
; ost = x[0] & 7
mov     eax, [x]
and      eax, 7
mov     [ost], eax
call      ExitProcess
end begin





Дополнительные команды


Следующие две команды позволяют осуществить поиск первого установленного в 1 бита операнда. Поиск можно произвести как с начала так и от конца операнда:
bsf
операнд_1,операнд_2 (Bit Scaning Forward) - сканирование битов вперед.
Команда просматривает (сканирует) биты операнд_2
от младшего к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0.
Пример:

mov     al,02h
        bsf     bx,al   ;bx=1
        jz      m1      ;переход, если al=00h
        ...
 

bsr операнд_1,операнд_2
(Bit Scaning Reset) — сканирование битов в обратном порядке. Команда просматривает (сканирует) биты операнд_2
от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения.
При этом важно, что позиция первого единичного бита слева отсчитывается все равно относительно бита 0. Если все биты операнд_2
равны 0, то флаг нуля zf
устанавливается в 1, в противном случае флаг zf
сбрасывается в 0. Заметим, что эти команды для tasm32 не компилируются


Определение типа битовой структуры


Общий вид типа:
record Имя поле1, поле2, ...
Для задания поля используется запись:
Имя: Ширина[= Значение]
Поля задаются, начиная с поля, соответствующего старшим разрядам. Суммарная ширина не может превосходить 32 бит. Если суммарная ширина не равна полному числу байтов, то число располагается в младших разрядах. Старшие разряды обнуляются.


Выделение памяти под битовую структуру


Общий вид директивы выделения памяти:
Тип структуры   Имя данного <Значения полей>
                                                { Значения полей }
Значения полей задаются как для обычной структуры в угловых скобках (позиционный метод) или в фигурных (вместе с именем поля). Если значение поля не задано, оно обнуляется.


Особенности использования битовых структур


Правила:
Если в команде используется имя данного, само
 значение данного используется.
Если в команде задано имя поля, то результатом является константа сдвига, которая показывает положение поля в записи.
Если в команде используется операция mask имя поля или mask имя типа, то формируется маска для выделения поля (записи).
Если в команде используется операция width имя поля или width имя типа, то формируется ширина поля (записи) в битах.


Пример использования битовой структуры


Дата создания файла задается в виде:
день :                   5 битов;
месяц :        4 бита;
год:             7 битов.
Год задает разность между текущим годом и 1970[13]
годом.
Значения для текущей даты упаковать в структуру и
проверить правильность упаковки.
ideal
p486
model  flat
extrn    ExitProcess:proc
dataseg
record data1 y:7, m:4, d:5
d1 data1          <>
day      db        1
month  db        11
year     dw       2000
codeseg
begin:
movzx ax, [day]
movzx bx, [month]
mov     cl, m
shl        bx, cl
or         ax, bx
mov     bx, [year]
sub       bx, 1970
mov     cl, y
shl        bx, cl
or         ax, bx
mov     [d1], ax           
mov     ax, [d1]
mov     bx, mask d
and      bx, ax
mov     [day], bl
mov     bx, mask m
and      bx, ax
mov     cl, m
shr       bx, cl
mov     [month], bl
mov     bx, mask y
and      bx, ax
mov     cl, y
shr       bx, cl
add      bx, 1970
mov     [year], bx
call       ExitProcess
end begin
Использование битовых структур позволяет сделать программу более мобильной. При изменении размера полей достаточно изменить только определение структуры, все остальные команды изменять не надо.


Типы программ


Одномодульная и многомодульная.
Одномодульная программа - в одном файле. Многомодульная состоит из нескольких файлов.


Использование функций


Функции позволяют оформлять независимые  участки
программы в виде отдельных модулей, что значительно упрощает  отладку программы и уменьшает ее размер. Ниже будут рассмотрены команды  для  работы  с  функциями,  структура  функций и способы передачи параметров. Вначале даются общие положения для  функций.  Команды для работы с процедурами(функциями)
Основные команды для работы с функциями приведены в табл. 11.1, а директивы в табл. 11.2
Таблица 11.1 Команды для работы с функциями

Назначение
Общий вид команды
Выполняемые действия
Вызов функции
call операнд[14]
push EIP
MOV EIP, операнд
Возврат в вызывающую программу
ret [Число]
Esp+=Число
Pop EIP

Таблица 11.2. Директивы для работы с функциями

Назначение
Общий вид директивы
Заголовок функции
имя proc или proc имя
Директива конца
[Имя] endp или endp [Имя]

Общий вид функции для режима masm:
имя  proc
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
имя  endp
Общий вид функции для режима ideal:
proc имя
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
endp имя 
Функция  одномодульной программы должна быть частью сегмента кодов. Может быть задано несколько функций  последовательно.При выполнении программ переход к функции необходимо   выполнять только по команде call, а не в результате естественного  выполнения команд, поэтому функции помещаются или  в  начало  сегмента кода до точки входа, или в конце после команд для выхода из программы.


Передача параметров через регистры


Для задания параметров или их адресов используются регистры.
Пример 1. Составить вызывающую программу и функцию для вычисления наибольшего общего делителя для двух целых положительных чисел с помощью алгоритма Эвклида.
Для данной программы исходными данными являются 2 целых числа, результатом является значение наибольшего общего делителя. Параметрами являются значения 2-х чисел и адрес результата. Пусть исходные данные передаются через регисты EAX, EBX, а адрес результата через регистр ECX
IDEAL
p586
MODEL FLAT
extrn ExitProcess:proc
DATASEG
X          DD      150
Y          DD      120
Z          DD      ?
CODESEG
begin:
mov     eax, [X]
mov     ebx, [Y]
mov     ecx, offset Z
call      gcd
call      ExitProcess
; Процедура
proc     gcd
push     edx
; if (x cmp     eax, ebx
jge       short m1
xchg     eax, ebx
; if (y==0) return x
m1:
test       ebx, ebx
je         short break
;           while(1)
for:
xor       edx, edx
div       ebx
test       edx, edx
je         short break
mov     eax, ebx
mov     ebx, edx
jmp      for
break:
mov     [ecx], ebx
pop      edx
ret
endp    gcd
end      begin
Достоинство способа - наиболее быстрый способ. Недостаток - недостаточное число регистров.
Пример 2. Составить процедуру для вычисления  длины  строки  с нулевым завершителем. Пусть адрес начала строки в регистре EBX,  а
адрес длины строки возвращается через регистр EAX.
; Процедура для вычисления длины строки с нулевым
; завершителем. Пусть адрес начала строки в регистре
; EBX, а адрес длины строки - через регистр EAX.
ideal
P486
MODEL  flat
extrn ExitProcess:proc
DATASEG
str1  db  'Это первая строка', 0
str2  db  'А это вторая', 0
n     dd  ?, ?
CODESEG
begin:
lea       ebx, [str1]
lea       eax, [n]
call      strlen
lea       ebx, [str2]
lea       eax, [n+4]
call strlen
call      ExitProcess
proc strlen
push ecx edx
xor  ecx, ecx
for:
mov     dl, [ebx]  ; while (str[i])ax++;
and      dl, dl
je         short lend; конец строки
inc       ecx
inc       ebx
jmp  for
lend:
mov     [eax], ecx
pop      edx ecx
ret
endp strlen
end  begin
Недостатки использования регистров для передачи параметров:
1. Можно использовать только для передачи данных длиной 1, 2, 4 байта или адресов:
2. Количество регистров ограничено.
Для преодоления недостатков можно все параметры записать в структуру и передать адрес начала структуры в единственном регистре или использовать для передачи параметров стек.


Передача параметров через общую область памяти


Т.к. в одномодульной программе  вызывающая программа и функция  расположены в одном файле,  функция может непосредственно использовать переменные программы.
Пример.
Составить функцию для обмена местами значения двух переменных.
ideal
P486
MODEL  flat
extrn ExitProcess:proc
DATASEG
x          dd        5
y          dd        3
CODESEG
begin:
call      swap
call      ExitProcess
proc swap
push eax ebx
mov     eax, [x]
mov     ebx, [y]
mov     [y], eax
mov     [x], ebx
pop  ebx eax
ret
endp swap
end  begin
Недостатки использования переменных из сегмента данных:
1. Функция должна использоваться только для переменных с заданными именами, использовать ее для других переменных нельзя.
2. Этот способ не применим для многомодульных программ
Общей областью можно считать стек, в который помещаются параметры. Рассмотрим особенности передачи параметров через стек.
При передаче параметров через стек возникает проблема доступа к ним. Из всех режимов адресации выберем режим, связанный с адресацией в стеке. В этом режиме в  качестве  базисного  регистра  используется регистр EBP, именно этот регистр  будем  применять  для задания элемента стека. С другой стороны, указатель  стека,  т.е. адрес последнего занесенного элемента в стек расположен в  регистре ESP. Чтобы иметь доступ к данным стека используются команды:
                push ebp      ;  сохранение EBP
                mov ebp, esp ;  EBP=ESP
     Эти команды должны присутствовать в любой функции, в кото-
рой параметры передаются через стек.


Передача в списке параметров простых переменных


     Параметры можно разделить на параметры - исходные данные  и  параметры-результаты. Для первых передаются значения, а для вторых - адреса. Чтобы уменьшить число передаваемых параметров,  для  массивов передаются адреса, а не значения элементов, даже  для  массивов исходных данных. Чтобы стек не переполнялся,  после  использования  параметры должны быть извлечены из стека. Эту функцию, называемую  очисткой стека, обычно выполняет функция . Для этого в команде ret задается количество байтов, используемых для параметров, т.е. используется формат команды ret <число>. В этом случае сначала очищается стек, а затем выполняется возврат в вызывающую программу.
     Ниже рассмотрены примеры использования функций с  передачей
параметров через стек. Примеры отражают приемы  передачи  простых переменных (исходных данных и результатов), массивов,  структур, и адресов функций.
      Пример 1. Составить функцию вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e.
;Функция для вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e
                        IDEAL
                        P586
MODEL  FLAT
EXTRN ExitProcess:proc
                 DATASEG
          a            DD  5
          b            DD  3
          c            DD  ?
          d   DD  4
          e   DD  -5
          f   DD  ?
          CODESEG
          begin:
             
              push [a]      ; запись в стек a, b
              push [b]
              lea  eax, c      ;Формирование & c
              push  eax    ; и запись его в стек
              call summa  ; Вызов функции
             
              push  [d]     ; запись в стек d, e
              push  [e]
              lea  eax, [f]     ; Формирование & f
              push eax
              call summa  ; Вызов функции
            call ExitProcess
             
          

proc summa
0
EBP
push ebp
4
EIP
mov  ebp, esp
8
&c
Push eax ebx
12
B
mov  eax, [ebp+16]  ; a
16
A
Add  eax, [ebp+12]  ; a+b
mov  ebx, [ebp+8]  ; &c
mov  [ebx], eax    ; c=a+b
Pop ebx eax ebp
Ret 12
Endp summa
End begin

В примере показана передача простых переменных (исходных данных и результатов). Показано состояние стека после обращения к функции для вычисления c = a + b.  Анализ  программы  показывает, что передача параметров с последующей очисткой стека, передача управления процедуре требует дополнительных команд.  Эти  же действия выполняются при программировании на языке высокого уровня. Поэтому, прежде чем писать функцию  на любом языке, подумайте, эффективна ли она! Решение предыдущего примера без  функции явно предпочтительней!


Передача в списке параметров одномерных массивов


Для одномерных массивов – и исходных данных и результатов – передаются адреса. Это исключает необходимость копирования исходных массивов.
    
Пример 1.  Составить функцию для объединения двух строк  и использовать ее для объединения строк, содержащих слова "first  " и "second " в одну строку.
IDEAL
P586
MODEL FLAT
Extrn ExitProcess:proc
DATASEG
str1   db   'first ', 0
str2   db   'second', 0
rez    db    (rez-str1-1) dup (?)
CODESEG
begin:
push     offset str1 offset str2 offset rez  ; передача адресов массивов
call strcat  ;вызов процедуры
call ExitProcess

proc strcat;
0
EBP
push ebp;
4
EIP
mov  ebp,  esp;
8
REZ
Push eax ebx ecx;
12
STR2
; for (I=0; str1[I]; I++) rez[I]=str1[i];
16
STR1
Mov EAX, [EBP+16];
Mov EBX, [EBP+8];
For1:
Mov CL, [EAX]
Test Cl, cl
Je break1
Mov [EBX], cl
Inc eax
Inc ebx
Jmp For1
Break1:
Mov EAX, [ebp+12]
For2:
Mov CL, [EAX]
Mov [EBX], cl
Test Cl, cl
Je break2
Inc eax
Inc ebx
Jmp For2
Break2:

Pop ecx ebx eax ebp
Ret 12
Endp
End begin
     В этом примере признаком конца исходных данных является  нулевой завершитель (символ с кодом 0). В результирующую строку завершитель записывается после копирования второй строки. Для  массивов исходных данных (str1, str2) и массива результата (rez) передаются адреса. Это делается для  экономии  стекового  простран-
ства и числа команд, необходимых для передачи параметров.
Пример 2.  Составить программу для записи в третий массив тех чисел первого, которых нет во втором.
ideal
p386
model   flat
extrn ExitProcess:proc
dataseg
x          dd        1,2,3,4,5,6

ALаааааааааааааааааа - Ёхчєы№ЄрЄ - срщЄ
AXаааааааааааааааааа - Ёхчєы№ЄрЄ - ёыютю
EAXаааааааааааааааа - Ёхчєы№ЄрЄ Ц фтющэюх ёыютю
EDX, EAXаааааа - Ёхчєы№ЄрЄ Ц 2 фтющэvї ёыютр
аааа ¦ЁшьхЁ 3.а TюёЄртшЄ№ ЇєэъЎш¦ фы  ёЁртэхэш  "фышээvї" ўшёхы. LєэъЎш  фюыцэр тючтЁр•рЄ№ 0, хёыш ўшёыр ёютярфр¦Є, 1 Ц хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую ш Ц1 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
xааааааааа ddааааааа 1000 dup (0ffffffffh)
nааааааааа ddааааааа (n-x)/4
yааааааааа ddааааааа 1000 dup (0ffffffffh)
resаааааа ddааааааа ?
t1аааааааа ddааааааа ?
t2аааааааа ddааааааа ?
codeseg
begin:
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp1
movаааа [res], eax
rdtsc
subаааа eax,ecx
movаааа [t2], eax
call ExitProcess
;for (i=n-1; i>=0; i--){ааааааааааааааааааааааа ebpааааа 0
;ааа if (x[i]>y[i]) return -1;а ааа eipаааааа 4
;ааа if (x[i] ;}ааааааааааааааааааааааааааааа аааааааааааааа xааааааааа 12
;return 0;ааааааааааааааааааааа аааааааааааааааааааааа nааааааааа 16
proc ааа m_cmp
push ebp;
movа ebp,а esp;
Pushа ebx ecx edx
movаааа ecx, [ebp+16]; n
movаааа eax, [ebp+12]; &x
movаааа ebx, [ebp+8] ; &y
fori:
movаааа edx, [eax+ecx*4-4]
cmpаааа edx, [ebx+ecx*4-4]
jbаааааааа short m1; x[i]< y[i]
jaаааааааа short m2; x[i]> y[i]
loopаааа fori
subаааааа eax, eax
m1:
movаааа eax, -1
jmpааааа short m3
m2:
movаааа eax, 1
m3:
popааааа edx ecx ebx ebp
Retаааааа 12
Endp
proc ааа m_cmp1
push ebp;
movа ebp,а esp;
Pushаа ecx esi edi
movаааа ecx, [ebp+16]; n
movаааа esi, [ebp+12]; &x
leaаааааа esi, [esi + ecx*4-4]
movаааа edi, [ebp+8] ; &y


leaаааааа edi, [edi + ecx*4-4]
std
repeаааа cmpsd
jbаааааааа short m11; x[i]< y[i]
jaаааааааа short m12; x[i]> y[i]
subаааааа eax, eax
m11:
movаааа eax, -1
jmpааааа short m13
m12:
movаааа eax, 1
m13:
cld
popааааа edi esi ecx ebp
Retаааааа 12
Endp
end begin
¦рьхЄшь, ўЄю лЎшЇЁv¬ ўшёыр ёЁртэштр¦Єё , эрўшэр  ёю ёЄрЁ°хщ ЎшЇЁv. ¦рцфр  ЎшЇЁр ёЁртэштрхЄё  ъръ схччэръютюх ўшёыю. TЁртэхэшх яЁюфюыцрхЄё  фю Єхї яюЁ, яюър эрщфхь Ёрчэvх ЎшЇЁv шыш эх ёЁртэшь тёх ЎшЇЁv ўшёыр.
+сЁрЄшЄх тэшьрэшх эр эютvх ъюьрэфv фы  юяЁхфхыхэш  ъюышўхёЄтр ЄръЄют фы  тvяюыэхэш  ЇєэъЎшщ. ¦юьрэфр RDTSC (Read Time-stamp counter) - тvяюыэ хЄё  фы  яЁюЎхёёюЁют, эрўшэр  ё PENTIUM MMX ш тючтЁр•рхЄ т ЁхушёЄЁх EAX
+Єюсv юяЁхфхышЄ№, яюффхЁцштрхЄё  ыш фрээр  ъюьрэфр фы  яЁюЎхёёюЁр, шёяюы№чєхЄё  ъюьрэфр CPUIDЧCPU Identification
¦Єр ъюьрэфр Єръцх яючтюы хЄ юяЁхфхышЄ№ яЁюшчтюфшЄхы  яЁюЎхёёюЁр, ёхьхщёЄтю, ъюЄюЁюьє юэ яЁшэрфыхцшЄ, ьюфхы ш тхЁёшш, р Єръцх фЁєує¦ шэЇюЁьрЎш¦. -ы  юяЁхфхыхэш  Єшяр тvфртрхьющ шэЇюЁьрЎшш шёяюы№чєхЄё  ЁхушёЄЁ EAX ( Єрсы 11.3). ¦юьрэфр CPUID ьюцхЄ тvяюыэ Є№ё  фы  ы¦сюую єЁютэ  яЁштшыхушщ.
TрсышЎр 11.3

Tїюфэvх фрээvх
Tvїюфэvх фрээvх
EAX = 0
EAX Ц ьръёшьры№эюх чэрўхэшх, ъюЄюЁюх ьюцэю чрфртрЄ№ эр тїюфх (юсvўэю 2)
EBX, EDX, ECX Ц яЁюшчтюфшЄхы№ Ц -ы  PENTIUM ёЄЁюър лGenuineIntel¬ ЁрёяЁхфхыхэр Єръ: EBXа ?756e6547h (* "Genu", сєътр G т BL *)
EDXа ?49656e69h (* "ineI", сєътр i т -DL *)
ECXа ?6c65746eh (* "ntel", сєътра n т CL *)
EAX = 1
EAX Ц тхЁёш  яЁюЎхёёюЁр (Єшя, ёхьхщёЄтю, ьюфхы№, step)
EBX - ЁхчхЁт
ECX- ЁхчхЁт
EDX Ц шэЇюЁьрЎш  юс юёюсхээюёЄ ї яЁюЎхёёюЁр
EAX = 2
EAX ЦшэЇюЁьрЎш  ю ъ¤°х
EBX -ЦшэЇюЁьрЎш  ю ъ¤°х
ECX- ЦшэЇюЁьрЎш  ю ъ¤°х
EDX Ц ЦшэЇюЁьрЎш  ю ъ¤°х

¦рёёьюЄЁшь ёюфхЁцшьюх Єюы№ъю эхъюЄюЁvї сшЄют ЁхушёЄЁр EDX:
23 Ц яюффхЁцштрхЄё  MMX Ц Єхїэюыюуш ;
18 Ц яЁюЎхёёюЁ яюффхЁцштрхЄ 96 сшЄэvщ єэшъры№эvщ эюьхЁ яЁюЎхёёюЁр.
4 Ц яюффхЁцштрхЄё  ъюьрэфр RDTSC
¦ЁшьхЁ 4. TюёЄртшЄ№ ЇєэъЎш¦, ъюЄюЁр  тючтЁр•рхЄ юЄтхЄ 1, хёыш ъюьрэфр RDTSC яюффхЁцштрхЄё  ш 0 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
resаааааа ddааааааа ?
codeseg
begin:
callааааа IsReadCount
movаааа [res], eax
call ExitProcess
proc ааа IsReadCount
movаааа eax, 1
cpuid
movаааа eax, 1
testаааааа edx, 1 shl 4
jnzааааааа short m1
subаааааа eax, eax
m1:
Retаааааа
Endp
end begin

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


При рассмотрении механизма определения адреса для двухмерного массива было замечено, что для определения адреса текущего элемента массива необходимо знать размер одного элемента
и длину строки (или количество элементов в одной строке). Поэтому в списке параметров необходимо обязательно задавать непосредственно длину строки или параметр, который позволит вычислить эту длину.
Пример 1 . Составить главную программу и функцию для умножения заданного столбца матрицы на заданное значение.
ideal
p686
model   flat
extrn ExitProcess:proc
dataseg
matr     dd        1, 1, 1, 1
            dd        2, 2, 2, 2
            dd        3, 3, 3, 3
value    dd        4
col       dd        1
n          dd        4; количество столбцов
m         dd        3; количество строк           
codeseg
begin:
push     offset matr [n] [m] [value] [col];       ebp      0
call      mc_mul;                                                  eip   4
call ExitProcess;                               col          8
proc     mc_mul;                                 value     12
push     ebp;                                    m  16
mov     ebp, esp;                               n 20
;                                               matr    24
;for(i=0; i ;   matr[i][col]*=value;
push    eax ebx ecx esi
mov     ecx, [ebp+16]
mov     esi, [ebp+24]
mov     ebx, [ebp+8]; col
lea       esi, [esi+ebx*4]; & начала
mov     ebx, [ebp+20]
shl        ebx, 2; Размер строки
mov     edi, [ebp+12]
for1:
mov     eax, [esi]
mul      edi
mov     [esi], eax
add      esi, ebx
loop     for1
pop      esi ecx ebx eax ebp
Ret       5*4
Endp
end begin
Пример 2.
Для заданного массива строк определить длины строк
ideal
p686
model   flat
extrn ExitProcess:proc
dataseg
s1         db        'a', 0
s2         db        'bb', 0
s3         db        'ccc', 0
s4         db        'dddd', 0
array   dd        s1, s2, s3, s4
n          dd        (n-array)/4

lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp
movаааа ebp, esp;ааааааааааааааааааааааааааааааааа TT+¦
;for (i=0; i ;аа for (j=0; array[i][j]; j++);аааа ааааааа eipаааааа 4
;аа len[i]=j;аааааааааааааааааааааааааа аааааааааааааа lenаааааа 8
;}ааааааааааааааааааааааааааааааааааааа аааааааааааааааааа nааааааааа 12
;аааааааааааааааааааааааааааааааааааааа ааааааааааааааааааа arrayаа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]
fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
end begin
+сЁрЄшЄх тэшьрэшх эр ЁрсюЄє ё рфЁхёюь ьрёёштр рфЁхёют!
¦ЁшьхЁ 3. LяюЁ фюўшЄ№ ьрёёшт ёЄЁюъ т яюЁ фъх єсvтрэш  фышэ ёЄЁюъ.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
s1аааааааа dbааааааа 'a', 0
s2аааааааа dbааааааа 'bb', 0
s3аааааааа dbааааааа 'ccc', 0
s4аааааааа dbааааааа 'dddd', 0
arrayаа ddааааааа s1, s2, s3, s4
n аааааааа ddааааааа (n-array)/4
lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
push ааа offset array [n] offset len
callааааа SortLenааааааааааа
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp;ааааааааааааааааааааааааааааааааааа
movаааа ebp, esp;ааааааааааааааааааааааааааа ааа
;for (i=0; i ;аа for (j=0; array[i][j]; j++);ааааааа eipааааааааааа 4
;аа len[i]=j;аааааааааааааааааааааааааа lenааааааааа 8
;}ааааааааааааааааааааааааааааааааааааа nаааа 12
;аааааааааааааааааааааааааааааааааааааа arrayаааааааааа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]


fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
proc SortLen
pushаааа ebp
movаааа ebp, esp
pushad
;for (i=1; i ;аа r=x[i];аааааааааааааааааааааааааааааааааааа аааааааааааааааааааааааааааааа eipаааааа 4
;аа for (j=i-1; j>=0; j--)аааааааааааааааааааааааааааааааааааааааааааааа arrayаа 8ааааааааа
;аааааааааа if (x[j] ;аа x[j+1]=r;аааааааааааааааааааааааааааааааааа аааааааааааааааааааааааааааа lenаааа 16
;}
movаааа eax, 1; i
movаааа ebx, [ebp+8]; array
movаааа edx, [ebp+16]; len
@@fori:
pushаааа ebp
movаааа esi, [ebx+eax*4]; r1
movаааа edi, [edx+eax*4]; r2
movаааа ecx, eax; j
@@forj:
decаааааа ecx
jsааааааааа short @@breakj
cmpаааа [edx+ecx*4], edi
jaeаааааа short @@breakj
movаааа ebp, [edx+ecx*4]
movаааа [edx+ecx*4+4], ebp
movаааа ebp, [ebx+ecx*4]
movаааа [ebx+ecx*4+4], ebp
jmp @@forj
@@breakj:
movаааа [ebx+ecx*4+4], esi
movаааа [edx+ecx*4+4], edi
popааааа ebp
incаааааа eax
cmpаааа eax, [ebp+12]
jbeаааааа @@foriааа аааааа ааа
popad
popааааа ebp
retааааааа 12
endp
end begin
+сЁрЄшЄх тэшьрэшх эр шёяюы№чютрэшх ьхЄюъ тшфр @@шь . ¦Єю Єръ эрчvтрхьvх ыюъры№эvх ьхЄъш, юэш шёяюы№чє¦Єё  фы  юуЁрэшўхэш  юсырёЄш фхщёЄтш  ьхЄюъ. +сырёЄ№ фхщёЄтш  ьхЄъш Ц ЇєэъЎш  шыш юсырёЄ№ ьхцфє фтєь  юсvўэvьш ьхЄърьш.
¦Ёш ёюЁЄшЁютъх ьхэ ¦Єё  ьхёЄрьш эх ёрьш ёЄЁюъш, р шї рфЁхёр ш фышэv. ¦Єю ьюцхЄ сvЄ№ чэрўшЄхы№эю ¤ЇЇхъЄштэхщ юсьхэр ьхёЄрьш ёЄЁюъ, хёыш юэш фышээvх.
а
Tръ ъръ яЁшэЎшяv ЁрсюЄv ё фтєїьхЁэvьш ш ьэюуюьхЁэvьш ьрёёштрьш юфшэръютv, ёюёЄртыхэшх ЇєэъЎшщ фы  ьэюуюьхЁэvї ьрёёштют тvяюыэ хЄё  яю Єхь цх яЁртшырь, ўЄю фы  фтєїьхЁэюую ьрёёштра

Передача в списке параметров структур


Пример 1. Составить главную программу и функцию для вычисления суммы двух комплексных чисел.
p686
IDEAL
MODEL flat
extrn ExitProcess:proc
DATASEG
struc   complex
real    dd      ?
im      dd      ?
ends
n1   complex    <1, 1>
n2   complex    <2, 2>
n3   complex    ?
CODESEG
begin:
push    [n1.im]
push    [n1.real]
push    [n2.im]
push    [n2.real]
push    offset n3
call    csum
call      ExitProcess
proc     csum
push     ebp
mov      ebp, esp
push      eax ebx
mov       eax, [(complex ebp+20).real]
add       eax, [(complex ebp+12).real]
mov       ebx, [ebp+8]
mov       [(complex ebx).real], eax
mov       eax, [(complex ebp+20).im]
add       eax, [(complex ebp+12).im]
mov       [(complex ebx).im], eax
pop       ebx eax ebp
ret       20
endp   csum
end     begin
 Обратите внимание на запись компонентов комплексного числа в стек. Это число записывается так, чтобы младшая часть числа находилась в младших адресах стека.
Аналогично со структурами используются объединения.


Передача в списке параметров функций


Функции передаются, если необходимо решить класс задач, которые различаются только списком используемых функций, причем эти функции имеют одинаковый список параметров. Пример – вычисление определенного интеграла для разных подинтегральных функций.
Для функций передаются их адреса. Для обращения к функции, заданной своим адресом, используется косвенный вызов.
Пример . Составить главную программу и функции вычисления:
Min –  для вычисления минимального из двух заданных целых чисел;
Max –  для вычисления максимального из двух заданных целых чисел;
MinMax - для вычисления минимального или максимального из двух заданных целых чисел, в зависимости от заданной в списке параметров функции;
ideal
p686
model   flat
extrn ExitProcess:proc
dataseg
x          dd      5
y          dd      3
z1        dd      ?; min
z2        dd      ?; max
array            dd      Min, Max
codeseg
begin:
push  [x] [y]
mov   eax, [array]; min
push  eax
call  MinMax
mov   [z1], eax
push  [x] [y]
mov   eax, [array+4]; min
push  eax
call  MinMax
mov   [z2], eax
call    ExitProcess;                               ebp     0
proc Min;                                        eip      4
push  ebp;                                       y          8
mov   ebp, esp;                              x          12
mov   eax, [ebp+12]
cmp    eax, [ebp+8]
jl        short @@m1
mov   eax, [ebp+8]
@@m1:
pop    ebp
ret     8
endp
;                                                           ebp     0
proc Max;                                       eip      4
push  ebp;                                       y          8
mov   ebp, esp;                              x          12
mov   eax, [ebp+12]
cmp    eax, [ebp+8]
jg       short @@m1
mov   eax, [ebp+8]
@@m1:
pop    ebp
ret     8
endp
;                                                                                   ebp     0
proc MinMax;                                                        eip      4
push  ebp;                                                               &fun 8
mov   ebp, esp;
;                                                                                   y          12
push  [dword ebp+16] [dword ebp+12];   x          16
mov   eax, [ebp+8]
call  eax
pop    ebp
ret     12
endp
end begin


Классификация параметров. Способы передачи параметров процедурам


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


Составление функций с переменным списком параметров


Пример. Составить главную программу и функцию для вычисления максимального числа для одного, двух и более чисел.
p586
IDEAL
MODEL fLat
extrn ExitProcess:proc
DATASEG
x          dd      1
y          dd      2
z          dd      3
u         dd      4
res     dd      ?
CODESEG
begin:
push  [u] [z] [y] [x] 4;      ebp     0
call  max;                           eip      4
add    esp, 5*4;                    4          8 ; Постоянные
mov   [res], eax;               [x]      12
call ExitProcess;                 [y]       16; Переменные
proc     max;                                   [z]       20
push     ebp;                                    [u]      24
mov      ebp, esp;
push              ebx ecx edx
mov   eax, [ebp+12]; x
mov   ecx, [ebp+8]; n
dec    ecx
jecxz            @@m2
lea     ebx, [ebp+16]; & переменной части списка
@@for1:
mov   edx, [ebx]
cmp    eax, edx
jge     short @@m1
mov   eax, edx
@@m1:
add    ebx, 4
loop @@for1
@@m2:
pop    edx ecx ebx ebp
ret
endp
end     begin
Обратите внимание на:
1. Формирование адреса начала переменной части списка;
2. Порядок передачи параметров в стек – начинаем с переменных параметров, чтобы постоянные параметры были записаны с постоянным смещением в вершине стека;
3. очистку стека, которую выполняет вызывающая программа, т.к. только она «знает», сколько передала параметров;


Составление рекурсивных функций на ассемблере


     Функция называется рекурсивной, если она обращается к  самой себе. Рассмотрим составление рекурсивной функции на  примере  вычисления факториала.
          Пример. Составить функцию вычисления Y = N!,
если
                         Y = 1, если N = 0,
                         Y = 1 * 2 ... * N, если N > 1
Если N < 0 функция должна возвращать -1, что говорит о неправильном аргументе.
          Соответствующая функция на языке С имеет вид:
          unsigned fact(int n){
                  if(n < 0) return -1;
                  if(n == 0)return 1
                  return (n * fact(n - 1));
          }
;Использование рекурсий в ассемблер программах
       
 ideal
        p586
        model flat
        extrn ExitProcess:proc
        dataseg
n       dd    4
x       dd    ?
        codeseg
       proc     fact
       push     ebp
       mov      ebp,esp; [ebp+8]-n
       push     ebx ecx edx
       mov      ebx, [ebp+8]
       test     ebx,ebx
       js       m1; <0
       jz       m2; ==0
       mov      ecx, ebx
       dec      ebx
       push     ebx
       call     fact
       mul      ecx
       jmp      short m3
m1:    mov      eax,0
       jmp      short m3
m2:    mov      eax,1
m3:    pop      edx ecx ebx
       pop      ebp
       ret      4
       endp     fact
begin:
       push     [n]
       call     fact
       call     ExitProcess
       end  begin
     "Прокрутите" программу, чтобы увидеть, как  изменяется  состояние стека. Вы увидите, что рекурсивные функции требуют,  чтобы размер стека был не меньше K * N, где  K-число  байт  стека,  используемых при каждом вызове, а N  -  число  вызовов  рекурсивной функции


Одномодульные программы


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


Использование общих областей памяти


При составлении многомодульных программ программисты часто используют внешние переменные, что позволяет один раз выделить память под переменную и один раз ее инициализировать. Такие переменные определяются в сегменте данных одного из модулей и задаются в директиве PUBLIC этого модуля, а во всех модулях, где используются эти переменные, они определяются  в директиве EXTRN.
Пример. Составить функцию вычисления суммы двух чисел, передавая исходные данные через внешние переменные.
; главная программа
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
y          dd        3
x          dd        5
z          dd        ?
public x
public y
codeseg
begin:
call       Summa
mov     [z], eax
call ExitProcess
end begin
; Функция
ideal
p686
model flat
extrn x:dword, y:dword
codeseg
proc Summa
public Summa;                       
mov     eax, [x]
add      eax, [y]
ret       
endp
end


Особенности использования внешних функций


     Внешняя функция транслируется отдельно  от  вызывающей программы. Это позволяет не транслировать каждый раз модули, в которых не было изменений. Упрощается изолированная от всей системы отладка отдельных модулей, что допускает участие в разработке системы группы программистов. Не следует заботиться  о  локальных именах программы, например метках, которые  могут  совпадать для различных модулей. И,  наконец,  появляется  возможность стыковки разноязыковых модулей.
    Чтобы объединить несколько  модулей  (эту  функцию  выполняет компоновщик) в одну программу, необходима специальная  информация для вызывающей и вызываемой программ. Рассмотрим эту информацию.
            В Ы З Ы В А Ю Щ А Я   П Р О Г Р А М М А .
    1.  Необходимо знать, что вызываемый модуль является внешним. Для задания этого этого используется директива:
             extrn  имя функции :  proc
.Директива записывается вне сегментов,  в этом случае компоновщик будет искать определение этого  имени  во всех сегментах, пока не найдет. 
    2.  Если вызывающая программа резервирует память под  данные, которые должны использоваться в функции,  адреса  этих данных должны быть определены как адреса типа public,  т.е.  доступные(известные)  другим сегментам.
    Общий вид директивы public:
                    public  имя1, имя2, ....
    Директива public, если она необходима, задается в том сегменте, где определено  это  имя.  Директиву  можно  использовать  не только для переменных, но и для меток.
            В Ы З Ы В А Е М А Я    П Р О Г Р А М М А
     1. Имя функции должно  быть  задано  в  директиве public
                 public  имя_ функции
     2. Если функция использует данные, память под которые выделена в  другой  программе,  то  в  этой  программе  используется директива
              extrn  определение1, определение2,...
     Общий вид определения для передаваемых данных:
                  имя : тип : количество ,
где
     имя - имя данного, память под которое выделена в другом  модуле;

аааааааааааааааа ; ¦хрышчрЎш  рыуюЁшЄьр
аааааааааааааааа ; TюёёЄрэютыхэшх шёяюы№чєхьvї ЁхушёЄЁют
аааааааааааааааааааааааа .........
ааааааааааааааааааааааааааа ret
ааааааааааааааааа шь а endp
аааааааааааааааааааааа end
аааа T ЇєэъЎшш, т юЄышўшх юЄ уыртэющ яЁюуЁрььv, т фшЁхъЄштх end рфЁхё Єюўъш тїюфр эх чрфрхЄё .
аааа +с•шщ тшф тvчvтр¦•хщ яЁюуЁрььv :
аааааааааааааааа .MODELа шь 
ааааааааааааааааааааааа ......
аааааааааааааааа extrnа шь _яЁюЎ : proc
ааааааааааааааааааааааа ......
аааааааааааааааа .CODE
аааааааа begin:
аааааааааааааааааааааааа .......
ааааааааааааааааа endа begin
ааа -шЁхъЄштv public ш extrn т ¤Єшї яЁюуЁрььрї ьюцэю чрьхэшЄ№ фшЁхъЄштющ global шь _яЁюЎ:proc. =ю ьv тёх-Єръш Ёхъюьхэфєхь трь шёяюы№чютрЄ№ фшЁхъЄштv public ш extrn,а Є.ъ.а юэша яючтюы ¦Єа сюыхх уыєсюъю шчєўшЄ№ ьхїрэшчь ёт чш ьюфєыхщ. Lьхээю ¤Єш фшЁхъЄштv эршсюыхх ўрёЄю шёяюы№чє¦Єё  т юяєсышъютрээvї яЁюуЁрььрї. ¦юёых Єюую, ъръ тv єтхЁхээю яюўєтёЄтєхЄх ёхс  т шёяюы№чютрэшш ¤Єшїа фшЁхъЄшт, ьюцэю шї чрьхэшЄ№ фшЁхъЄштющ global.
аааа -ы  ёючфрэш  шёяюыэ хьющ яЁюуЁрььvа шча эхёъюы№ъшїа ьюфєыхщ, тvяюыэ хЄё  Ёрчфхы№эр  ЄЁрэёы Ўш  фы  ърцфюую ьюфєы :
ааааааааааааааааа Tasm32 ./ml /ziа шь _ьюфєы 1
ааааааааааааааааа Tasm32 /ml /ziа шь _ьюфєы 2
ааааааааааааааааа ...
ш ъюьяюэютър тёхї ьюфєыхщ тьхёЄх:
ааааааааааааааааа tlink32 /v шь _ьюфєы 1 шь _ьюфєы 2 ....import32.lib
аааа -ы  ЇюЁьшЁютрэш  юЄырфюўэющ шэЇюЁьрЎшша шёяюы№чє¦Єё а Єха цх ъы¦ўш, ўЄю ш фы  юфэюьюфєы№эvї яЁюуЁрьь.
аааа ¦ЁшьхЁv ёюёЄртыхэш  ш шёяюы№чютрэш  тэх°эшїа ЇєэъЎшщ.
аааа ¦ЁшьхЁ 1. TюёЄртшЄ№ тэх°э¦¦ ЇєэъЎш¦ фы  тvўшёыхэш а z=x+y.
¦рЁрьхЄЁv яхЁхфртрЄ№ ўхЁхч ёЄхъ.
;Tэх°э   ЇєэъЎш  фы  тvўшёыхэш  z=x+y. ¦рЁрьхЄЁv
;ааааааааааааааа яхЁхфр¦Єё  ўхЁхч ёЄхъ.
;+ыртэр  яЁюуЁрььр
; Їрщы main.asm
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
xааааааааа ddааааааа 5
yааааааааа ddааааааа 3
zаааааааааа ddааааааа ?
codeseg
begin:
pushаааа [x] [y]
callааааа Summa
movаааа [z], eax
call ExitProcess
end begin
; Lрщы PROC.asm
ideal
p586
modelаа flat
codeseg
procа _Summa
public _Summa
arg x:dword, y:dword
pushаааа ebp
movаааа ebp, esp
MOVаа EAX, [x]
addааааа eax, [y]
popааааа ebp
retааааааа
endp
end
аааа -ы  ЄЁрэёы Ўшш ¤Єшї Їрщыют шёяюы№чє¦Єё  ъюьрэфv
аааааааааааааааааааааааа Tasm32 /zi /mlа main
аааааааааааааааааааааааа Tasm32 /zi /mlа proc
аааааааааааааааааааааааа Tlink32 /vа main proc import32.lib,
т Ёхчєы№ЄрЄх тvяюыэхэш  ъюЄюЁvї яюыєўшь Їрщы main.exe.

Реентерабельное программирование


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


Использование локальных областей памяти


Для выделения локальной области используется стек.
Структура стека с локальной областью:
; Сохранение регистров
; Локальная область памяти
EBP
EIP
; Фактические параметры
Для выделения локальной области используется команда:
SUB ESP, Размер локальной области
Для освобождения локальной области используется 2 способа:
  • ADD ESP, Размер локальной области

  • MOV   ESP, EBP

  • Последний способ более защищен от ошибок, связанных с асинхронным использованием стека
    Структура функции с локальной областью:
    Proc    Имя
    Push    EBP
    Mov     EBP, ESP
    SUB     ESP, Размер локальной области
    PUSH  Регистры

    POP    Регистры
    MOV   ESP, EBP
    POP    EBP
    RET     [Константа]
    Пример. Составить главную программу и функцию для преобразования числа в символьное представление.
    Алгоритм.
    void itoa (unsigned x){
      int numbers[] = {10000, 1000, 100, 10, 1};
      for (int I=0; I<5; I++){
          Y[I]= x/numbers[i] + ‘0’;
          X%= numbers[i];
    }
    }
    ideal
    p586
    model   flat
    codeseg
    proc itoa;                                 ebp                  0
    public itoa;                              eip                   4
    ;;                                              y                      8
    ;;                                              x                      12
    push  ebp
    mov   ebp, esp
    sub   esp, 5*4
    push  eax ebx ecx edx esi
    mov   [numbers], 10000
    mov   [numbers+4], 1000
    mov   [numbers+8], 100
    mov   [numbers+12], 10
    mov   [numbers+16], 1
    mov     ecx, 5
    mov     eax, [ebp+12]
    mov     ebx, [ebp+8]
    lea     esi, [numbers]
    for1:
    xor     edx, edx
    div     [dword ptr esi]
    add     al, '0'
    mov     [ebx], al
    inc     ebx
    add     esi, 4
    mov     eax, edx
    loop    for1
    mov     [byte ptr ebx], 0
    pop     esi edx ecx ebx eax
    mov     esp, ebp
    pop     ebp
    ret     8
    endp    itoa
    end
    Составьте самостоятельно главную программу для этого примера и проверьте ее на машине!


    Особенности использования команд ENTER и LEAVE


    Анализ функций, составленных ранее, показывает, что в функции требуется:
    Сохранить и сформироваить значение регистра EBP;
    Выделить локальную память.
    Для обеспечения этих операций можно использовать команду ENTER (вход), а для автоматического освобождения локальной области и восстановления регистра EBP – команду LEAVE (выход).
    Общий вид команды ENTER:
    ENTER оп1, оп2
    Оп1- определяет размер локальной области в байтах (константное выражение);
    Оп2 – уровень вложенности функций (Константное выражение 0 .. 31). Для каждого уровня вложенности копируется фрейм стека при создании нового фрейма. Это делается для обеспечения возможности доступа к параметрам и локальной области внешних функций.
    Команда ENTER , если она используется, является первой командой функции, а команда LEAVE – непосредственно перед командой RET.
    Если уровень вложенности равен 0, то вставляются команды:
    PUSH  EBP
    MOV   EBP, ESP
    SUB     ESP, оп1.
    Для уровня вложенности один или больше, процессор сохраняет указатели на фреймы стеков для предыдущих уровней (содержимое регистра EBP для всех предыдущих уровней).
    Команда LEAVE выполняет действия, обратные действиям ENTER, в том числе восстановление регистра EBP.


    Директива ARG


    Используется для задания имен и типов формальных параметров. Параметры записываются, начиная с вершины стека. Общий вид директивы:
    ARG параметр1, параметр2, … [= переменная]
    Параметры задаются в виде:
    Имя:Тип[:Количество]
    В качестве имени используется любое уникальное имя.
    Тип может быть стандартным и нестандартным. В качестве стандартных используются типы: BYTE, WORD, DWORD, PWORD, FWORD, QWORD, TBYTE. В качестве нестандартных можно задавать типы структур и объединений.
    Количество задается константным выражением и определяет количество элементов данного типа. Используется, если передается массив, а не его адрес. По умолчанию Количество
    = 1. Переменной, заданной в директиве присваивается размер области параметров в стеке. Это значение вычисляется компилятором и используется в команде RET.
    Пример. Составить функцию для вычисления значения Y=A*X+B для данных длиной 32 бита. Результат длиной 4 байта
    ; Главная программа
    ideal
    p686
    model flat
    extrn ExitProcess:proc
    extrn Fun:proc
    dataseg
    a          dd        3
    x          dd        5
    b          dd        7
    y          dd        ?
    codeseg
    begin:
    push     [a] [x] [b]
    call      Fun
    mov     [y], eax
    call ExitProcess
    end begin
    ; Функция
    ideal
    p686
    model flat
    codeseg
    proc     Fun
    public  Fun
    arg       b:dword, x:dword, a:dword = z
    push     ebp
    mov     ebp, esp
    mov     eax, [a]
    mul      [x]
    add      eax, [b]
    pop      ebp
    ret        z
    endp
    end


    Директива LOCAL


    Директива используется для определения структуры локальной области и ее размера. Сама локальная область не создается. Для ее создания необходима команда SUB ESP, Размер или команда ENTER.
    Общий вид директивы:
    LOCAL Параметр1, Параметр2,… = Переменная
    Локальные параметры задаются точно также, как формальные параметры, т.е. задается имя, тип и, возможно, количество. Размер локальной области присваивается на этапе компиляции переменной, заданной в конце директивы . Переменная используется в командах выделения локальной области.
    Пример. Составить главную программу и функцию на ассемблере
    int Fun (int x){
       int a, b[2];
       a=x; b[0]=b[1] = 2 *x;
       return a+ b[0] + b[1];
    }
    ; Главная программа
    ;Int Fun (int x){
    ;   Int a, b[2];
    ;   a=x; b[0]=b[1] = 2 *x;
    ;   return a+ b[0] + b[1];
    ;}
    ideal
    p686
    model flat
    extrn ExitProcess:proc
    extrn Fun:proc
    dataseg
    x          dd        5
    y          dd        ?
    codeseg
    begin:
    push     [x]
    call      Fun
    mov     [y], eax
    call ExitProcess
    end begin
    ; Функция
    ;Int Fun (int x){
    ;   Int a, b[2];
    ;   a=x; b[0]=b[1] = 2 *x;
    ;   return a+ b[0] + b[1];
    ;}
    ideal
    p686
    model flat
    codeseg
    begin:
    proc Fun
    public Fun
    arg x:dword = z1
    local a:dword, b:dword:2=z2
    push     ebp
    mov     ebp, esp
    sub       esp, z2
    mov     eax, [x]
    mov     [a], eax
    mov     [b], eax
    add      [b], eax
    mov     [b+4], eax
    add      [b+4], eax
    mov     esp, ebp
    pop      ebp
    ret        z1
    endp
    end





    Вставка в Си команд на ассемблере


         Рекомендации и правила записи вставок.
         1. Команда должна начинаться с ключевого слова asm. При  использовании С++
    несколько подряд идущих команд могут быть объединены в блок, например
          asm {
          .......
          }
         Некоторые версии трансляторов с языка СИ требуют  слово  asm перед каждой командой или вместо этого слова используют _asm  или др.
         2. Вставлять можно все допустимые машинные команды и  директивы, не определяющие работу транслятора. Так не допускаются  директивы
            segment и ends ;
            proc    и endp;
            assume  и end
            и др.
    3. В ассемблерной части программы допускается  использование данных Си программы всех классов (локальные,  внешние,  статические и регистровые). Данные могут быть объявлены в ассемблерной части программы, как внешние переменные для использования в ассемблерной части программы. Эти переменные операторы языка «С» не видят, например:
    #include
    int y;
    asm {
    x       dd      ?
    }
    int main(int argc, char* argv[])
    {
            asm{
              mov   [x], 5
              mov   eax,[x]
              mov   [y],eax
            }
            cout << y;
            char cc;
            cin >> cc;
            return 0;
    }
    Здесь показано как использовать данные, определенные в участках программы на С и на ассемблере.
         4. Для некоторых версий языка С[15], если в ассемблерной части есть команда перехода, то  метка записывается по правилам Си, например:
               asm  jmp  label
               ............
              label:asm xor ax, ax
         5. Комментарии записывают по правилам  Си.  Символ  ';'  используется  в качестве конца команды, а не начала  комментария.  В строке может быть записано несколько команд, например :
           asm {push ax; push bx; push cx}
    После последней или единственной команды символ ';'  можно  не ставить. Команды должны быть написаны по правилам MASM, не IDEAL.
         6. В ассемблерной части программы нельзя  изменить  содержимое сегментных регистров, регистров для работы со стеком (esp,  ebp),  а  также  регистров esi,  edi,  если  используются регистровые переменные[16].



         7. Если встречается хотя бы одна ассемблерная вставка, необходим дополнительный шаг трансляции с языка Си на ассемблер.  Для задания  транслятору  режима  перевода  в  ассемблерный  код  используется директива
                 #pragma inline
    Если директивы нет, возможна повторная трансляция, если  встречена команда на ассемблере.
         Ниже приведен пример программы с ассемблерными вставками
              #pragma inline
              #include
              int summa (int x, int y){
                int z;
              asm {
                  mov  eax, [x]
                  add  eax, [y]
                  mov  [z], eax
              }
              return z;
              }
              main(){
               printf(" сумма = % d\n", summa(5, 3));
              }
         Достоинство метода: наиболее прост с точки  зрения  программиста.
         Недостаток метода. Нельзя  отдельно  оттранслировать  ассемблерную часть.

    Использование отдельных функций на ассемблере


    Функция должна удовлетворять соглашениям по вызову для языка С++.
    Соглашения по вызову различаются по следующим параметрам:
    Правила формирования внутреннего имени функции;
    Правилами передачи параметров;
    Правилами очистки стека.
    При формировании имени возможен регистро – чувствительный или регистро нечувствительный режим, возможно формирование дополнительных символов для обеспечения свойства перегрузки функции.
    Для обеспечения возможности использования переменного списка параметров параметры могут записываться в стек, начиная с конца списка, для обеспечения максимальной скорости работы – для передачи параметров используются регистры
    Очистку стека может выполнять вызывающая программа или функция.
    В табл. 11.4 представлены соглашения по вызову и свойства этих соглашений.
    Если файл с программой имеет расширение CPP, то по умолчанию используется режим 1, если файл с расширением С++ - режим 2. Если в программе с расширением СPP необходимо использовать функцию с соглашением С, заголовок этой функции имеет вид:
    Extern “C” заголовок;
    Если необходимо задать  заголовочный файл, который можно использовать в программе на С и СРР, то применяют директивы
    #ifdef __cplusplus
    extern “C” {
    #endif
    заголовок 1;
    заголовок 2;

    #ifdef __cplusplus
    }
    #endif
    Пример. Составить вызывающую программу и функции вычисления разности для двух чисел, используя все возможные соглашения по вызову.
    Таблица 11.4. Соглашения по вызову, принятые в С++


    п/п
    Тип соглашения
    Как задается
    Внутреннее имя функции
    Порядок передачи параметров
    Очистка стека
    Переменный список параметров
    1.
    Cpp
    -
    @имя$q[17]…
    ¬
    Вызывающая программа
    Да
    2.
    С
    Extern “C”…
    _имя
    ¬
    Вызывающая программа
    Да
    3.
    Fastcall
    Тип __fastcall …
    @имя$qqr…
    EAX, EDX, ECX
    Вызывающая программа
    Нет
    4.
    Stdcall
    Тип
    __stdcall …
    @имя$qqs…
    ¬
    Функция
    Нет
    5.
    Pascal
    Тип
    __pascal …
    @ИМЯ$Q…
    ®
    Функция
    Нет

    // +ыртэр  яЁюуЁрььр
    #pragma hdrstop
    #include
    #include
    //---------------------------------------------------------------------------
    USEASM("std.asm");
    USEASM("std1.asm");
    USEASM("std2.asm");
    USEASM("std3.asm");
    USEASM("std4.asm");
    //---------------------------------------------------------------------------
    #pragma argsused
    int __stdcallа substract ( int x, int y, int *z);
    intаа substract1 (int x, int y, int *z);
    intаа __fastcall substract2 (int x, int y, int *z);
    extern "C" intаа substract3 (int x, int y, int *z);
    intаа pascal substract4 (int x, int y, int *z);
    intаа __fastcall sum (int *x, int y);
    int main(int argc, char **argv)
    {
    ааааа int z;
    ааааа substract (3, 2, &z); printf ("%d\n", z);
    ааааа substract1 (3, 2, &z); printf ("%d\n", z);
    ааааа substract2 (3, 2, &z); printf ("%d\n", z);
    ааааа substract3 (3, 2, &z); printf ("%d\n", z);
    ааааа substract4 (3, 2, &z); printf ("%d\n", z);
    ааааа getchar ();
    ааааааа return 0;
    }
    ; stdcall
    ideal
    p586
    model flat
    codeseg
    procааа @substract1$qiipi
    public @substract1$qiipi
    argаа a:dword, b:dword, c:dword=s
    pushааа ebp
    movаааа ebp, esp
    pushааа ebx
    movаааа eax, [a]
    subаааа eax, [b]
    movаааа ebx, [c]
    movаааа [ebx], eax
    popаааа ebx ebp
    retаааа
    endp
    end
    ;fastcall
    ideal
    p586
    model flat
    codeseg
    procааа @substract2$qqriipi
    publicа @substract2$qqriipi
    pushааа esi
    movаааа esi, eax
    subаааа esi, edx
    movаааа [ecx], esi
    popаааа esi
    retаааа
    endp
    end
    а
    ;extern C
    ideal
    p586
    model flat
    codeseg
    procааа _substract3
    publicа _substract3
    argаа a:dword, b:dword, c:dword=s
    pushааа ebp
    movаааа ebp, esp
    pushааа esi
    movаааа esi, [a]
    subаааа esi, [b]
    movаааа ecx, [c]
    movаааа [ecx], esi
    popаааа esi ebp
    ret
    endp
    end
    ; pascal
    ideal
    p586
    model flat
    codeseg
    proc @SUBSTRACT4$QIIPI
    public @SUBSTRACT4$QIIPI
    argаа c:dword, b:dword, a:dword=s
    pushааа ebp
    movаааа ebp, esp
    pushааа esi
    movаааа esi, [a]
    subаааа esi, [b]
    movаааа ecx, [c]
    movаааа [ecx], esi
    popаааа esi ebp
    retаааа s
    endp
    end

    Стыковка с языками высокого уровня


         Существует два способа стыковки:
         1. Вставка в Си программу команд на ассемблере;
         2. Программа на Си вызывает функцию на ассемблере;
             


    Особенности создания функций для включения их в DLL


    Для включения функции в DLL в качестве экспортируемых функций директива PUBLIC должна быть заменена директивой PUBLICDLL.
    Пример. Создать экспортируемую функцию для вычитания целых чисел, которая должна входить в DLL как экспортируемая функция. Пусть функция удовлетворяет соглашению по вызову extern “C”:
    Файл с функцией proc.asm
    ;extern C
    ideal
    p586
    model flat
    codeseg
    proc    _substract3
    publicdll  _substract3
    arg     a:dword, b:dword, c:dword=s
    push    ebp
    mov     ebp, esp
    push    esi
    mov     esi, [a]
    sub     esi, [b]
    mov     ecx, [c]
    mov     [ecx], esi
    pop     esi ebp
    ret
    endp
    end
    Главная программа, использующая функцию substract3:
    #pragma hdrstop
    #pragma argsused
    #include
    extern "C"  __declspec (dllimport) int substract3 (int, int, int *);
    int main(int argc, char* argv[])
    {
            int z;
            substract3 (5, 3, &z);
            cout << z;
            char c;
            cin>>c;
            return 0;
    }
    //---------------------------------------------------------------------------
    К проекту долженбыть поключен файл с расширением LIB, соответствующий сформированной DLL.
    Аналогично можно использовать DLL библиотеку в режиме загрузки с помощью функции Load Library.


    Особенности использования ассемблерных функций в качестве функций – членов классов


    Пусть задан класс:
    class STRING{
        char *s;
        public:
        STRING (char * c){
              s = new char[strlen(c)+1];
              strcpy (s, c);
        }
        ~STRING (){
       delete []s;
          }
        public:
        int len ()
    };
    Пусть функцию определения длины строки необходимо определить на ассемблере. Определим ее сначала на С. Получим файл:
    #pragma inline
    #include "mystr.h"
    int STRING::len(){
      int i;
       for (i=0; s[i]; i++);
       return i;
    }
    Здесь директива #pragma inline задана с целью формирования ассемблерного кода. В результате трансляции получим файл на ассемблере, основна часть которого приведена ниже:
                .386p
                ifdef ??version
                if    ??version GT 500H
                .mmx
                endif
                endif
                model flat
                ifndef   ??version
                ?debug            macro
                endm
                endif
                ?debug            S "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
                ?debug            T "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
    _TEXT segment dword public use32 'CODE'
    _TEXT ends
    _DATA            segment dword public use32 'DATA'
    _DATA            ends
    _BSS    segment dword public use32 'BSS'
    _BSS    ends
    $$BSYMS        segment byte public use32 'DEBSYM'
    $$BSYMS        ends
    $$BTYPES      segment byte public use32 'DEBTYP'
    $$BTYPES      ends
    $$BNAMES     segment byte public use32 'DEBNAM'
    $$BNAMES     ends
    $$BROWSE    segment byte public use32 'DEBSYM'
    $$BROWSE    ends
    $$BROWFILE            segment byte public use32 'DEBSYM'
    $$BROWFILE            ends
    DGROUP       group   _BSS,_DATA
    _TEXT segment dword public use32 'CODE'
    @STRING@len$qv    segment virtual
    @@STRING@len$qv proc     near
    ?live16385@0:
                ?debug L 4
                push      ebp
                mov       ebp,esp
                push      ecx
                ?debug L 6
    @1:
                xor       eax,eax
                mov       dword ptr [ebp-4],eax

    ааааааааааа jmpаааааа short @3
    @2:
    ааааааааааа incаааааа dword ptr [ebp-4]
    @3:
    ааааааааааа movаааааа edx,dword ptr [ebp+8]
    ааааааааааа movаааааа ecx,dword ptr [edx]
    ааааааааааа movаааааа eax,dword ptr [ebp-4]
    ааааааааааа cmpаааааа byte ptr [ecx+eax],0
    ааааааааааа jneаааааа short @2
    ааааааааааа ?debug L 7
    ааааааааааа movаааааа eax,dword ptr [ebp-4]
    ааааааааааа ?debug L 8
    @6:
    @5:
    ааааааааааа popаааааа ecx
    ааааааааааа popаааааа ebp
    ааааааааааа ret
    ааааааааааа ?debug L 0
    @@STRING@len$qv endp
    Е
    ¦ЁюрэрышчшЁєхь яюыєўхээvщ Їрщы. Lч Їрщыр тшфэю, ўЄю шь  ЇєэъЎшш Ц ўыхэр ЇюЁьшЁєхЄё  шч шьхэш ъырёёр, шьхэш ЇєэъЎшш ш Єшяют ярЁрьхЄЁют. ¦Єю шь  ыєў°х тёхую тч Є№ шч юс·хъЄэюую Їрщыр тvчvтр¦•хщ яЁюуЁрььv.
    -ы  юс·хъЄр ъырёёр яхЁхфрхЄё  рфЁхё, уфх эрїюфшЄё  рфЁхё эрўрыр ¤Єюую юс·хъЄр, Є.х. яхЁтющ тэєЄЁхээхщ яхЁхьхээющ ¤Єюую ъырёёр (ярЁрьхЄЁ ЇєэъЎшш). -ы  фюёЄєяр ъ ёрьющ яхЁхьхээющ шёяюы№чєхЄё  фтх ъюьрэфv:
    movаааааа edx,dword ptr [ebp+8]
    movаааааа ecx,dword ptr [edx]; &s
    +•х Ёрч юсЁрЄшЄх тэшьрэшх эр эх¤ЇЇхъЄштэюх шёяюы№чютрэшх ыюъры№эющ яхЁхьхээющ тэєЄЁш ъырёёр!
    ¦Ёюьх яЁштхфхээvї тv°х ъюьрэф шёяюы№чє¦Єё  фюяюыэшЄхы№эvх ъюьрэфv. Lэрышч ъюьрэф яЁюуЁрььv яюърчvтрхЄ, ўЄю яЁю•х яхЁхяшёрЄ№ ъюьрэфv юяЁхфхышЄхы  ЇєэъЎшш Ц ўыхэр ъырёёр тэєЄЁш ЇєэъЎшш эр  чvъх T++.
    ¦хрышчрЎш  ЇєэъЎшш
    #include "mystr.h"
    int STRING::len(){
    а asm {
    аааа movааааааа eax, -1
    аааа movааааааа ecx, [ebp+8]
    аааа movааааааа ecx, [ecx]
    аааа for1:
    аааа incаа аааааeax
    аааа cmpааааааа byte ptr [ecx+eax], 0
    аааа jneа for1
    а }
    }
    TЁртэшЄх ¤ЇЇхъЄштэюёЄ№ ъюфр ё рёёхьсыхЁэющ тёЄртъющ ш схч эхх!

    Недостатки функций


    Для сравнительной оценки процедур и макросов сначала составим главную программу и функцию для простой задачи, а затем определим недостатки и покажем возможности использования макросов вместо процедур. Определим область применения функций и макросов.
    Пример . Составить главную программу и функцию для обмена местами двух целых чисел длиной 32 бита.
    ; Главная программа
    IDEAL
    p586
    model           flat
    extrn ExitProcess:proc
    dataseg
    first           dd      1
    second  dd      2
    codeseg
    begin:
    push    offset first
    push    offset second
    call    swap
    call      ExitProcess
    ; Функция
    proc    swap
    arg     a:dword, b:dword=s
    push    ebp
    mov     ebp, esp
    push    esi edi eax ebx
    mov     esi, [a]
    mov     edi, [b]
    mov     eax, [esi]
    mov     ebx, [edi]
    mov     [esi], ebx
    mov     [edi], eax
    pop     ebx eax esi edi
    pop     ebp
    ret     s
    endp    swap
    end     begin
    Анализ программы показывает, что для обращения к функции необходимо:
    *  передать параметры или их адреса (число команд не меньше числа передаваемых параметров);
    *  обратиться к функции (занесение адреса возврата и передача управления - сброс конвейера и декодирование команд заново - фактически 2 команды + сброс конвейера, что соответствует потере не менее 4 тактов).
    В самой функии необходимо:
    *  обеспечить доступ к параметрам (2 команды);
    *  восстановить стек  и передать управление вызывающей программе (2 команды + сброс конвейера).
    Таким образом, суммарные «накладные расходы» вызова функциии составляют N + 6 + 2 сброса конвейера, где N - число параметров. Дополнительные расходы по одной команде для параметров-результатов, т.к. необходимо использовать косвенные адреса. В данной программе исполняемая часть функции содержит всего 4 команды и использовать функцию с 10 (N = 2) дополнительными командами и 2 сбросами процессора очень плохо как с точки зрения времени выполнения, так и с точки зрения памяти! Лучше вставить требуемые команды для обмена прямо в программу.
    Таким образом, если число команд функции невелико, или если функция используется однократно, лучше функцию не применять!
    Для обеспечения вставки в требуемое место участка программы с настройкой с учетом списка параметров используются макросы


    Простейшие макросы


    Простейшие макросы используют директиву EQU для определения макроса. Директива используется для задания обозначений операторов или их частей. Такие макросы называются текстовыми.
    Пример. Пусть необходимо в программе использовать много диагностических сообщений, которые содержат общие части, например:
    msg1    db        ‘Ошибка в записи идентификатора’, 13, 10, 0
    msg2    db        ‘Ошибка в записи числа’, 13, 10, 0
    ···
    msgN   db        ‘Ошибка в записи выражения’, 13, 10, 0
    Для упрощения записи обозначим общие части сообщений m1(Ошибка в записи)  и m2(13, 10, 0). Для этого используются операторы:
    m1     EQU  < Ошибка в записи >
    m2     EQU  <13, 10, 0>
    Угловые скобки в записи означают, что данная запись рассматривается как единое целое, а не состоит из нескольких элементов.
    Тогда для задания msg1, msg2,... msgN требуются операторы:
    msg1    db        m1, ‘ идентификатора’, m2
    msg2    db        m1, ‘ числа’, m2
    ···
    msgN   db        m1, ‘ выражения’, m2
    Для проверки правильности составленных макросов составим программу для формирования строк:
    IDEAL
    p586
    model flat
    extrn ExitProcess:proc
    dataseg
    m1      EQU     < 'Ошибка в записи' >
    m2      EQU     <13, 10, 0>
    msg1    db      m1, ' идентификатора', m2
    msg2    db      m1, ' числа', m2
    msg3    db      m1, ' выражения', m2
    codeseg
    begin:
    call      ExitProcess
    end     begin
    Для просмотра сформированных строк войдите в режим DUMP и, используя CTRL/G, задайте просмотр области памяти, начиная с msg1. Вы увидите сформированные строки.
     


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


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


    Макроопределение (м/о)


    Общий вид (м/о):
    MACRO <Имя> [<Формальный параметр1>[, <Формальный параметр2>[, ...< Формальный параметрN>]]]
    Команда1
    Команда2
    ...
    КомандаM
    ENDM
    В режиме MASM <Имя>
    задается перед ключевым словом MACRO.
    Пример м/о для обмена местами данных:
    MACRO          swap    a,b
    push     eax, ebx
    mov     eax, a
    mov     ebx, b
    mov     a, ebx
    mov     b, eax
    pop      ebx eax
    ENDM
    М/о может быть записано в любом месте программы до использования


    Макрокоманда (м/к)


    Общий вид макрокоманды:
    <Имя м/о> [<Фактический параметр1>[, < Фактический параметр2>[, ... < Фактический параметрN>]]]
    количество формальных параметров, как правило, должно совпадать с числом фактических. Формальных параметров может быть больше, чем фактических (см. ниже).
    Пример м/к для м/о SWAP:
    SWAP  [first], [second]
    М/к должна быть записана в том месте программы, где требуется использование м/о.


    Макрорасширение (м/р)


    Для получения расширения необходимо в м/о подставить фактические команды макрорасширения. Фактические параметры м/к должны быть записаны так, чтобы получились синтаксически правильные команды. Макрорасширение формируется компилятором и подставляется в программу вместо м/к. В листинге программы можно увидеть команды макрорасширения.
    Пример. Составить программу для обмена местами данных длиной 32 бита с помощью макросов.
     
    IDEAL
    p586
    model           flat
    extrn ExitProcess:proc
    dataseg
    first           dd      1
    second             dd      2
    codeseg
    begin:
    MACRO   SWAP    a, b
    push    eax ebx
    mov     eax, a
    mov     ebx, b
    mov     a, ebx
    mov     b, eax
    pop     ebx eax
    endm
    SWAP    [first], [second]
    call      ExitProcess
    end    begin
    Откомпилируйте программу в режиме формирования листинга (ключ l). По листингу проанализируйте число сформированных команд для функции и макросов. Сделайте выводы по использованию макросов и функций.


    Макросы


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


    Правила записи параметров


    Ранее составлен макрос для обмена местами двойных слов. Если необходимо менять местами слова, будем использовать другой макрос:
    MACRO SWAPW        a, b
    push     ax bx
    mov     ax, a
    mov     bx, b
    mov     a, bx
    mov     b, ax
    pop      bx ax
    endm
    Если бы использовались функции, для каждого типа данных обязательно составлять новую функцию. Для макросов эти макроопределения могут быть объединены в одно, для этого необходимо научиться соединять параметры с постоянной частью или другими параметрами. Например, составляется м/о для работы с байтами или словами. В этом случае используются регистры ax или al cоответственно. Для задания этих регистров буква a используется как постоянная часть, а буквы x
    или l являются параметрами.
    Для объединения параметров с постоянной частью или другими параметрами используется знак &, т.е. запись имеет вид:
    Параметр & Постоянная часть;
    Постоянная часть & Параметр
    Параметр & Параметр,
    например a&Reg, где:
    a        -Постоянная часть;
    Reg    -Параметр.
    Составим м/о для обмена местами данных для целых длиной 2 и 4 байта.
    IDEAL
    p586
    model           flat
    extrn ExitProcess:proc
    dataseg
    firstd          dd      1
    secondd         dd      2
    firstw          dw      1
    secondw         dw      2
    codeseg
    begin:
    MACRO   SWAP    a, b, type
    push    type&ax type&bx
    mov     type&ax, a
    mov     type&bx, b
    mov     a, type&bx
    mov     b, type&ax
    pop     type&bx type&ax
    endm
    SWAP    [firstw], [secondw]
    SWAP    [firstd], [secondd], e
    call      ExitProcess
    end begi
    n
    А теперь попытаемся составить м/о таким образом, чтобы его можно было использовать и для байтов. В этом случае в качестве регистров можно использовать регистры al
    и bl, т.е. параметром является не только первая, но и последняя буква имени регистра:
    Программа для этого варианта имеет вид:
    IDEAL
    p586
    model           flat
    extrn ExitProcess:proc
    dataseg

    firstd          dd      1
    secondd dd      2
    firstw          dw      1
    secondw dw      2
    firstb          db      1
    secondb db      2
    codeseg
    begin:
    MACRO   SWAP    x, y, ft, st
    push    ft&ax ft&bx
    mov     ft&a&st, x
    mov     ft&b&st, y
    mov     x, ft&b&st
    mov     y, ft&a&st
    pop     ft&bx ft&ax
    endm
    SWAP    [firstb], [secondb], ,l
    SWAP    [firstw], [secondw],,x
    SWAP    [firstd], [secondd], e,x
    call      ExitProcess
    end begin
    Заметим, что в данном случае оставить формальные параметры a, b нельзя, т.к. постоянная часть и параметр совпадают.
    Значение фактического параметра вставляется всюду, даже во внуть строки, если используется со знаком &,  например

    MACRO error name
    Db ‘Ошибка. Файл  &name’
    endm
    dataseg
    msg1    error    first.asm
    end
    Имя макроопределения может совпадать с ключевыми словами, т.е. можно переопределить команды процессора, правда, в этом случае компилятор выдаст диагностическое сообщение о совпадении имен!
    Пример замены операции сложения на операцию вычитания:
    IDEAL
    p586
    model flat
    extrn ExitProcess:proc
    MACRO error name
    Msg db'Ошибка. Файл  &name'
    endm
    MACRO add a, b
    mov   ax, a
    sub   ax, b
    mov   a, ax
    endm
    dataseg
    error   first.asm
    codeseg
    begin:
    add     cx,dx
    call ExitProcess
    end     begin
    В этом примере вместо команды add       ax,bx используется команда sub     ax,bx.
    Допускается многократное переопределение макроса в программе. Действует последнее определенное.

    Определение внутренних меток


    Пусть необходимо в программе вычислять значения максимального элемента из двух заданных. В этом случае макросы предпочтительнее процедур
    MACRO          MAX    x, y, z
    push     eax
    mov     eax, x
    cmp     eax, y
    jle        m1
    mov     eax, y
    m1:
    mov     z, eax
    pop      eax
    endm
    Пусть в программе необходимо многократно использовать этот макрос. При повторном его использовании будет ошибка в связи с повтором метки. Для обеспечения использования внутренних меток используется директива LOCAL <Метка>, в этом случае компилятор формирует каждый раз уникальную метку. Директива LOCAL должна быть записана сразу после заголовка м/о.
    Пример. Составить программу для определения максимального элемента для трех чисел с помощью макросов.
    IDEAL
    p386
    MODEL   fLat
    extrn ExitProcess:proc
    dataseg
    x       dd      3
    y       dd      5
    z       dd      2
    u       dd      ?
    codeseg
    begin:
    MACRO   max     x, y, z
    local   m1
    push    eax
    mov     eax, x
    cmp     eax, y
    jge     m1
    mov     eax, y
    m1:
    mov     z, eax
    endm
    max     [x], [y], [u]
    max     [u], [z], [u]
    call ExitProcess
    end     begin
    При компиляции программы формируются метки вида ??0000, ??0001 и т.д.


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


    Если фактический параметр внутри содержит запятую или пробел, параметр должен быть заключен в скобки <>, например:
    IDEAL
    p586
    model   flat
    MACRO ERROR     msg, number
    m&number        db      '&msg',13,10,'$'
    endm
    dataseg
    error   <Ошибка в записи идентификатора>, 1
    error   <Ошибка в записи числа>, 2
    error   <Ошибка в записи числа, длина !> 6>, 3
    END
    Откомпилируйте этот пример с формированием листинга и проанализируйте созданный листинг.
    Если среди символов фактического параметра есть символы <>, перед ними ставится знак !, например для предыдущего макроса необходимо сформировать
    error    <Ошибка в записи идентификатора, длина !>6>, 3
    Если знак ! будет пропущен, первый знак > будет рассмотрен как конец параметра.
    Если вместо фактического параметра используется выражение, которое должно быть вычислено до подстановки, перед параметром используется знак % например:
    MACRO          ARRAY            name, type, count
    name   d&type (count) dup(?)
    endm
    Для использования макроса для выделения памяти под матрицу с данными типа двойное слово размером 25*80 можно записать:
    IDEAL
    P586
    model   flat
    macro   array   name, type, count
    name    d&type  (count)    dup (?)
    endm
    dataseg
          array     matr, d, %25*80
    end


    Вложенные и рекурсивные макросы


    Макроопределение может включать в себя макрокоманды для других макросов или само определение других макросов.
    Пример. Пусть необходимо создать макросы для выполнения операций сложения или вычитания, причем операция определяется параметром. Тогда м/о для создания макросов имеет вид:
    MACRO          CREAT            x,y,z,kod
    MACRO          op&kod           x, y, z
    push     eax
    mov     eax, x
    kod      eax, y
    mov     z, eax
    pop      eax
    endm
    endm
    Для создания макроса для сложения используется макрокоманда:
    creat    x, y, z, add
    а для вычитания - макрокоманда
              creat    x, y, z, sub
    Рекурсивные макросы будут рассмотрены ниже


    Директивы условной трансляции


    IF <Выражение>
    <Операторы1>
    ELSE
    <Операторы2>
    ENDIF
    Операторы1 компилируются, если значение выражения истинно, в противном случае компилируются операторы2. Выражение
    считается ложным, если оно равно 0.
    В выражениях можно использовать логические операции AND, OR, NOT, знаки отношений: EQ(=), NE (¹), GT(>), GE(³), LT (<), LE (£), а также операции сдвига SHL, SHR.
    Есть специальные выражения, позволяющие определить транслируемые команды в зависимости от определенных переменных, передаваемых параметров и т.д.
    Список специальных выражений:
    ifdef <Переменная>
    - Если определена заданная переменная (т.е. для нее есть оператор equ или =);
    ifndef <Переменная> - Если не определена заданная переменная;
    ifidn <П1>, <П2>
    - если совпадают два параметра макрокоманды, или параметр совпадает с некоторой константой;
    ifdif <П1>, <П2>
    - если не совпадают два параметра макрокоманды, или параметр не совпадает с некоторой константой;
    ifb <П1> -
    если заданный параметр в макрокоманде пустой (не определен);
    ifnb <П1> - если заданный параметр в макрокоманде не пустой (определен);
    if1 - если выполняется первый просмотр;
    if2 - если выполняется второй просмотр.
    Пример. Составить макроопределение для вычисления у = |x| для данных длиной 32 бита.
    ideal
    p586
    model   flat
    extrn ExitProcess:proc
    MACRO   _ABS x, y
    local   m
    push    eax
    mov     eax, x
    test    eax, eax
    jns     short m
    neg     eax
    m:
    mov     y, eax
    pop     eax
    endm
    codeseg
    begin:
    mov     edx, -5
    _ABS     edx, ebx
    mov     edx, 5
    _ABS     edx, ebx
    call ExitProcess
    end     begin
    Имя
    _ABS используется, т.к. имя ABS зарезервировано. Макрос работает не верно, если результат в регистре EAX.
    Для использования макроса в случае результата в регистре EAX запишем его в виде:
    MACRO          _ABS x, y
    local    m
    ifidn ,
    r           equ     
    else     
    r           equ     
    endif
    push     r
    mov     r, x
    test       r, r
    jns        short m
    neg      r
    m:
    mov     y, r
    pop      r
    endm
    Директива условной трансляции позволяет выбрать рабочий регистр.


    Формирование сообщений об ошибках


    Пусть для исполнения программы требуется, чтобы переменная TEST была определена. Для проверки этого можно использовать операторы:
    IFNDEF TEST
    ERR «Определи переменную TEST!»
    ENDIF
    Если не определена переменная TEST, компилятор сообщает USER ERROR и сообщение «Определи переменную TEST!».


    Обзор средств условной трансляции


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


    Безусловное повторение


    Общий вид директивы:
    REPT count
    команды
    endm
    Команды повторяются заданное число раз.
    Пример. Пусть необходимо увеличить на 4 содержимое регистра eах, не изменяя кода переноса (бит с).
    Rept  4
    inc       ax
    endm


    Директива IRP


    IRP  Параметр, <П1, П2, ...>
    Команды
    ENDM
    Команды формируются столько раз, сколько задано параметров в угловых скобках. Каждый раз вместо параметра, заданного первым подставляется очередной параметр из списка заданных.
    Пример. Составитьмакроопределение для поиска максимального для заданных 5 чисел.
    ideal
    p586
    model flat
    extrn ExitProcess:proc
    MACRO MAX RES, X1, X2, X3, X4, X5
    local m
    ifidn ,
    reg     equ     ebx
    else
    reg     equ     eax
    endif
    push    reg
    mov     reg, X1
    IRP   X,
    local n
    cmp   reg, X
    jge    short n
    mov     reg, X
    n:
    endm
    mov     RES, reg
    endm
    codeseg
    begin:
    MAX eax, 5, 4, 3, 6, 1
    call ExitProcess
    end begin
    Если значение параметра, который подставляется, состоит из одной буквы, то используется макрос вида IRPC.


    Директива IRPC


    Общий вид директивы:
    IRPC Параметр, Строка
    Символы строки подставляются вместо значение параметра.
    Пример. Сформировать массив натуральных чисел
    ideal
    p586
    model flat
    dataseg
    irpc d, 0123456789
    c&d db            d
    endm
    end
    Для этого примера сформируйте листинг и проверьте полученный сегмент данных.


    Директива WHILE


    Общий вид директивы WHILE
    WHILE (Выражение)
    Команды
    ENDM
    Последний тип макроса используется, если необходимо развернуть цикл с известным числом повторений для исключения команд перехода, а значит возможного сброса конвейера.
    Пример. Составить программу сложения чисел заданного массива, не используя команд перехода
    ideal
    p586
    model flat
    extrn ExitProcess:proc
    dataseg
    a       dd      1,2,3,4,5
    s       dd      ?
    codeseg
    begin:
    xor    eax, eax
    xor   esi,esi
    i=1
    while i le 5
    add   eax, [a + esi]
    add   esi, 4
    i =  i + 1
    endm
    mov     [s], eax
    call ExitProcess
    end begin


    Обработка ошибок с помощью функции GetLastError


    Общий вид функции:
    DWORD GetLastError(VOID)
    Функция возвращает код ошибки (32 бита). Список кодов ошибок приведен в файле WinError.h. Здесь же приведен формат кода ошибки:

    Биты
    31-30
    29
    28
    27-16
    15-0
    № поля
    1
    2
    3
    4
    5

    1 – Код степени тяжести ошибки (severity code):
    00 – успех;
    01 – информация;
    10 – предупреждение;
    11 – ошибка.
    2 – Кем определена ошибка (Microsoft - 0, пользователь -1). Благодаря этому биту можно всегда определять коды ошибок, которые гарантированно не совпадут с системными кодами.
    3 – Зарезервировано. Должно быть 0.
    4 – Код подсистемы, к которой относится ошибка, например,
    #define FACILITY_WINDOWS                 8
    #define FACILITY_STORAGE                 3
    #define FACILITY_INTERNET                12
    #define FACILITY_CERT                    11
    Определяется MICROSOFT.
    5 – Код ошибки 0..65535.
    Ниже приведен фрагмент кода для вывода сообщения, соответствующего ошибке, нв языке, принятом по умолчанию для операционной системы. В этом фрагменте используются функции:
    FormatMessage
    – для формирования требуемого сообщения по номеру ошибки, который возвращает функция GetLastError. Для этой функции задается адрес буфер, а функция выделяет буфер в куче и записывает сформированное сообщение; тип формируемого сообщения (для нас – сообщение ОС - FORMAT_MESSAGE_FROM_SYSTEM и язык, принятый по умолчанию – макрос MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
    MessageBox – для вывода диалога с собщением;
    LocalFree – для освобождения памяти, выделенной для буфера.
    LPVOID lpMsgBuf;
     FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,    0,    NULL );
    MessageBox( NULL, lpMsgBuf, "GetLastError", MB_OK|MB_ICONINFORMATION);
    LocalFree( lpMsgBuf );


    Составление пользовательских функций со стандартной обработкой ошибок


    При составлении пользовательских функций со стандартной обработкой ошибок необходимо:
    1. Сформировать код ошибки, при этом в качестве кода можно выбрать стандартный код (см. файл WINERROR.H). Если среди кодов нет подходящего, то сформировать свой и определить его в своем заголовочном файле.
    2. В функции перед возвратом обратитьсяк функции SetLastError. Заголовок функции :
    VOID SetLastError (DWORD dwCode),
    Где dwCode – код ошибки.
    Именно этот код будет возвращен функцией GetLastError
    Пример. Пусть необходимо возвратитькод: Недостаточно памяти (ERROR_NOT_ENOUGH_MEMORY)
    #include
    bool MyFun (){
          SetLastError (ERROR_NOT_ENOUGH_MEMORY);
          return false;
    }
    int main(int argc, char* argv[])
    {
          bool b = MyFun ();
          if (!b){
             LPVOID lpMsgBuf;
           FormatMessage(
              FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,    0,    NULL );
              MessageBox( NULL, (char*)lpMsgBuf, "GetLastError",MB_OK|MB_ICONINFORMATION);
              LocalFree( lpMsgBuf );
          }
          return 0;
    }
    Приведенный код программы выведен сообщение о недостающей памяти, на языке, принятом по умолчанию для операционной системы.


    Обзор средств ввода-вывода


    При программировании на ассемблере используются:
    физический ввод-вывод;
    системные вызовы.
    Физический ввод-вывод предполагает работу с портами устройств или использование функций BIOS.  Системные вызовы - это использование функций операционной системы для организации ввода-вывода. В данном курсе рассматриваются только системные вызовы.
    Для системных вызовов используются функции WIN API. В случае ошибок эти функции возвращают значения, которые зависят от типа возвращаемого значения. Код ошибки, по которому можно определить ее причину, определяется с помощью функции  WIN API  GetLastError, которая должна быть вызвана непосредственно после функции, которая может быть завершена с ошибкой. Для составления безопасных программ рекомендуется после всех функций, которые могут завершаться с ошибкой, делать такую проверку!


    Функция MessageBox



    int MessageBoxA(
        HWND hWnd,         // Дескриптор окна (NULL для текущего)
    LPCTSTR lpText,        // Адрес выводимой строки
        LPCTSTR lpCaption,          // Адрес заголовка
        UINT uType            // Стиль. Определяет выводимые кнопки
       );      
    Допустимые стили:
    MB_ABORTRETRYIGNORE (2) 3 кнопки: Abort, Retry, и Ignore.
    MB_OK (0)     : Кнопка OK.
    MB_OKCANCEL (1)  Кнопки: OK и
    Cancel.
    MB_RETRYCANCEL (5)       Кнопки: Retry и Cancel.
    MB_YESNO (4)          Кнопки : Yes и No.
    MB_YESNOCANCEL (3)       3 кнопки: Yes, No, иCancel.
    Значения констант, которые надо задавать вместо приведенных имен при программировании на ассемблере заданы в скобках после определения константы. Их можно посмотреть в файле WINUSER.H
     Дополнительно, можно задавать иконки для окна и дополнительную информацию (см. файл WIN32.HLP):
    Возвращаемое значение:
    Функция возвращает 0, если не достаточно памяти для создания окна.
    При успешном завершении функция возвращает код нажатой клавиши: IDABORT       (3) нажата кнопка ABORT.
    IDCANCEL    (2) нажата кнопка CANCEL или клавиша ESC.
    IDIGNORE     (5) нажата кнопка IGNORE.
    IDNO  (7)нажата кнопка NO.
    IDOK (1)нажата кнопка OK.
    IDRETRY(4)нажата кнопка RETRY.
    IDYES(6)нажата кнопка YES.

    Пример программы для вывода сообщения “Hello, World”
    p586
    include win.inc
    model   flat
    extrn ExitProcess:proc
    extrn    MessageBoxA:proc
    dataseg
    mystr   db        'HelloWorld!', 0
    tit         db        'Message', 0
    codeseg
    begin:
    push     0
    push     offset tit
    push     offset mystr
    push     0
    call      MessageBoxA
    call ExitProcess
    end begin


    Функции и константы для работы с консолью


    При работе с консолью необходимо:
    определить номера стандартных устройств (см. файл winuser.h);
    определить дескриптор для заданного устройства;
    обратиться к функции ввода – вывода.
    Информация о номерах устройств и требуемых функциях приведена ниже.

    Номера стандартный устройств
    STD_INPUT_HANDLE         (DWORD)-10 ввод
    STD_OUTPUT_HANDLE     (DWORD)-11вывод
    STD_ERROR_HANDLE        (DWORD)-12 вывод ошибок
    Определение дескриптора устройства:
    HANDLE GetStdHandle(    DWORD nStdHandle     // номер устройства
       );      
    Если ошибка, функция возвращает INVALID_HANDLE_VALUE, которому соответствует значение –1.
    Ввод данных со стандартного устройства:
    BOOL ReadConsole(
        HANDLE hConsoleInput,   // дескриптор консоли
        LPVOID lpBuffer,   // адрес буфера, куда читать данные
        DWORD nNumberOfCharsToRead,          // количество читаемых символов
        LPDWORD lpNumberOfCharsRead,         // адрес количества прочитанных символов
        LPVOID lpReserved           // резерв, должен быть NULL
       );      
    Функция возвращает true в случае успеха и false в случае ошибки[19]
    BOOL WriteConsole(
        HANDLE hConsoleOutput,            // дескриптор консоли
        CONST VOID *lpBuffer,    // адрес буфера для записи
        DWORD nNumberOfCharsToWrite,          // количество символов
        LPDWORD lpNumberOfCharsWritten,     // адрес количества символов, которые действительно записаны
        LPVOID lpReserved           // Резерв, должен быть NULL
       );      

    Пример программы для ввода – вывода строки символов.
    ideal
    p386
    model   flat
    extrn ExitProcess:proc
    extrn GetStdHandle:proc
    extrn ReadConsoleA:proc
    extrn WriteConsoleA:proc
    dataseg
    include "win.inc"
    din  dd   0
    dout dd   0
    buf  db   80 dup (?)
    siz  dd   ?
    mystr     db   'Input string, please', 13, 10, 0
    codeseg
    begin:
    push     STD_INPUT_HANDLE

    push     str1
    push     0
    call      MessageBoxA
    endm
    Файл для проверки макроса
    Ideal
    p586
    model   flat
    extrn ExitProcess:proc
    extrn MessageBoxA:proc
    include "win.inc"
    dataseg
    t   db      'Information', 0
    text    db      'Hello, world!', 0
    codeseg
    begin:
    ShowMessage , , 0
    call    ExitProcess
    end     begin
     
    Пример 2. Составить функцию gets для ввода строки с клавиатуры, которая в результате формирует строку с нулевым завершителем. Признак конца вводимой строки- символ .
    ideal
    p586
    model  flat
    include "win.inc"
    extrn   GetStdHandle:proc
    extrn   ReadConsoleA: proc
    codeseg
    proc     gets
    public  gets
    arg       mystr:dword
    local    s:dword=r
    push     ebp     
    mov     ebp, esp
    sub       esp, r
    push    ebx ecx
    push     STD_INPUT_HANDLE
    call      GetStdHandle; ebp-0; eip-4; ;&str-8
    mov     ebx, [ebp+8]
    push     0
    lea       ecx, [ebp-4]
    push     ecx    
    push     80 ebx eax
    call      ReadConsoleA
    sub       [s], 2
    add      ebx, [s]
    mov     [byte ptr ebx], 0
    mov     eax, [ebp+8]
    pop     ecx ebx
    mov     esp, ebp
    pop      ebp
    ret        4
    endp
    end
    Главная программа для проверки функции ввода строки имеет вид:
    ideal
    p386
    model   flat
    extrn ExitProcess:proc
    extrn GetStdHandle:proc
    extrn ReadConsoleA:proc
    extrn WriteConsoleA:proc
    extrn    gets:proc
    dataseg
    include "win.inc"
    din       dd        0
    dout     dd        0
    buf       db        80 dup (?)
    siz        dd        ?
    mystr   db        'Input string, please', 13, 10, 0
    codeseg
    begin:
    push     STD_INPUT_HANDLE
    call      GetStdHandle
    mov     [din], eax
    push     STD_OUTPUT_HANDLE
    call      GetStdHandle
    mov     [dout], eax
    push     0 offset siz  22 offset mystr [dout]
    call      WriteConsoleA
    push     offset buf
    call      gets
    call ExitProcess
    end begin
    Пример 3. Составить функцию для вывода строки с нулевым завершителем на стандартное устройство


    Функция:
    Ideal
    p586
    model  flat
    include "win.inc"
    extrn   GetStdHandle:proc
    extrn   ReadConsoleA: proc
    extrn   WriteConsoleA:proc
    dataseg
    buffer   db        80 dup (?)
    codeseg
    proc     gets
    public  gets
    arg       mystr:dword
    local    s:dword=r
    push     ebp     
    mov     ebp, esp
    sub       esp, r
    push    ebx ecx
    push     STD_INPUT_HANDLE
    call      GetStdHandle;
    mov     ebx, [ebp+8]
    push     0
    lea       ecx, [ebp-4]
    push     ecx    
    push     80 ebx eax
    call      ReadConsoleA
    sub       [s], 2
    add      ebx, [s]
    mov     [byte ptr ebx], 0
    mov     eax, [ebp+8]
    pop     ecx ebx
    mov     esp, ebp
    pop      ebp
    ret        4
    endp
    proc     puts
    public  puts
    arg       mystr1:dword
    local    s1:dword=r1
    push     ebp     
    mov     ebp, esp
    sub       esp, r1
    push    ebx ecx edx esi
    push     STD_OUTPUT_HANDLE
    call      GetStdHandle
    mov     ebx, [ebp+8]; &
    ; len
    mov     edx, 0
    mov     esi, offset          buffer
    m2:
    mov     cl, [ebx+edx]
    mov     [esi+edx], cl
    test       cl, cl
    jz          short m1
    inc       edx
    jmp      m2
    m1:
    push     0
    lea       ecx, [ebp-4]
    push     ecx    
    push     edx
    push    offset buffer
    push     eax
    call      WriteConsoleA
    mov     eax, [ebp+8]
    pop     esi edx ecx ebx
    mov     esp, ebp
    pop      ebp
    ret        4
    endp
    end
    Главная программа для проверки функции вывода строки имеет вид:
    Ideal
    p386
    model   flat
    extrn ExitProcess:proc
    extrn GetStdHandle:proc
    extrn ReadConsoleA:proc
    extrn WriteConsoleA:proc
    extrn    gets:proc
    extrn   puts:proc
    dataseg
    include "win.inc"
    din       dd        0
    dout     dd        0
    buf       db        80 dup (?)
    siz        dd        ?
    mystr   db        'Input string, please', 13, 10, 0
    codeseg
    begin:
    push     offset mystr
    call      puts
    push     offset buf
    call      gets
    push     offset buf
    call      puts
    call ExitProcess
    end begin

    Функции ввода-вывода для стандартных устройств


    Известно, что кодировка символов , принятая по умолчанию, отличается для WINDOWS 95, WINDOWS NT. Тип кодировки задается буквой A в конце функции для однобайтной кодировки и буквой W для 2-х байтной. При создании программ на языке АССЕМБЛЕР необходимо учитывать это различие.  
    Для программного определения типа установленной системы используется функция GetVersion:

    DWORD GetVersion(VOID)
    Функция возвращает номер версии и подверсии установленной системы
    Для всех платформ младшее слово содержит номера версии (младший байт номер версии, старший – номер подверсии в 16 –ой системе счисления)
    Для различения платформ ОС используется старший бит и младший байт, которые принимают значения, приведенные в табл. 13.1.
    Таблица 13.1. Типы операционных систем


    Платформа
    Старший бит
    Младший байт
    Windows NT
    0
    3 или 4
    Windows 95
    1
    4
    Win32s, Windows 3.1
    1
    3

    Для Windows NT и  Win32s, оставшиеся биты старшего слова задают номер разработчика, для Windows 95 – резервируются.
    Пример использования функции на С++:

    DwVersion = GetVersion();
     
    dwWindowsMajorVersion =  (DWORD)(LOBYTE(LOWORD(dwVersion)));
    dwWindowsMinorVersion =  (DWORD)(HIBYTE(LOWORD(dwVersion)));
    if (dwVersion < 0x80000000)                // Windows NT
        dwBuild = (DWORD)(HIWORD(dwVersion));
    else if (dwWindowsMajorVersion < 4)        // Win32s
        dwBuild = (DWORD)(HIWORD(dwVersion) & ~0x8000);
    else         // Windows 95 --
        dwBuild =  0;
    К функциям ввода – вывода для стандартных устройств относятся:
    функция MessageBox для вывода диалогового окна с заданной информацией и заданными кнопками;
    функции для работы с консолью
    По умолчанию в качестве стандартных устройств ввода используется клавиатура, а вывода -монитор. Выводимую информацию можно разделить на информацию, соответствующую штатной работе программы, и информацию, соответствующую реакции на ошибки. По умолчанию для обоих типов информации используется монитор. Допускается переназначение стандартных устройств. Ниже приведены наиболее распространенные константы и функции[18].


    Ввод/вывод числовых данных


    Для ввода числовых данных необходимо:
    1. Ввести соответствующую строку.
    2. Преобразовать ее в числовой вид.
    Для вывода необходимо выполнить действия в обратном порядке.
    Преобразование строки в число.
    Пусть задана строка вида:
    [<Пробелы>] [<>][<Пробелы>] Цифра [Цифра Цифра...]
    Преобразовать ее в соответствующее ей число.
    Для преобразования последовательности цифр в число можно использовать схему Горнера. Например, пусть необходимо строку символов  «375»
    преобразовать в число. Представим число в виде:
    (3 * 10 + 7) * 10 + 5. Здесь 3, 7, 5 -цифры числа, а 10
    - основание системы счисления для исходного числа. Таким образом, алгоритм преобразования:
    r=0;
    for (i=n-1; i>=0; i--)
       r = r*10 +d[i];
    Так как в общем виде числа могут быть пробелы, которые надо игнорировать, напишем макрос для игнорирования этих символов. На вход макроса подается адрес символа строки, на выходе получаем адрес ненулевого символа
    macro skip address
    local    l1, l2
    ifidn ,

    reg       equ      esi
    else
    reg       equ      edi
    endif
    push     reg
    mov     reg, address
    l2:
    cmp     [byte ptr reg], ‘ ’
    jne       l1
    inc       reg
    jmp      l2
    l1:
    mov     address, reg
    pop      reg
    endm
    Данный макрос подключим к файлу win.inc
    Функции для преобразования строки в число и обратного преобразования представлены ниже.
    Преобразование чисел при выводе
    До вывода числа оно должно быть преобразовано из внутреннего представления в строковое. Для преобразования можно использовать следующие способы.
    1. Число делится на 10 и остаток от деления рассматривается как очередная цифра. Деление продолжается до тех пор, пока не получим нулевое значение. Недостаток способа: цифры числа получаем, начиная с младших, перед выводом необходимо инвертирование строки или формирование строки заданной длины.
    2. Число делится на 10000, 1000,... и получаем цифры числа, начиная со старших цифр. Недостаток. Требуется хранение массива или его программное формирование.

    Рассмотрим реализацию первого способа. Для хранения 32-битного десятичного числа требуется 10 цифр + знак + нулевой завершитель, т.е. 12 символов. Так как строка может занимать не все 12 символов, оставшиеся символы слева должны быть пробелами.
    Функция для преобразования числа в строку. Входное данное-32-битное число. Выходное - строка с нулевым завершителем
    Функции для преобразования числа при вводе и выводе и соответствующая главная программа представлены ниже:
    Ideal
    p586
    model              flat
    extrn ExitProcess:proc
    include "win.inc"
    dataseg
    my       db        '    25', 0
    val       dd        ?
    codeseg
    proc     atoi
    public  atoi
    arg       value:dword, mystr:dword = p
    push     ebp
    mov     ebp, esp
    push     ebx ecx edx esi
    mov     esi, [mystr]
    skip      esi
    mov     ebx, 1
    cmp     [byte ptr esi], '-'
    je         short    minus
    cmp     [byte ptr esi], '+'
    je         short plus
    jmp      prod
    minus:
    inc       esi
    neg      ebx
    jmp      prod
    plus:
    inc       esi
    prod:
    skip      esi
    xor       eax, eax;          r=0
    mov     ecx, 10
    for:
    cmp     [byte ptr esi], 0
    je         exit
    mul      ecx
    test       edx, edx
    jne       error
    mov     dl, [esi]
    sub       dl, '0'
    add      eax, edx
    inc       esi
    jmp      for
    exit:
    mul      ebx
    mov     esi, [value]
    mov     [esi], eax
    xor       eax, eax
    jmp      lend
    error:
    mov     eax, 1
    lend:
    pop      esi edx ecx ebx
    pop      ebp
    ret        p
    endp atoi
    begin:
    push     offset my offset val
    call      atoi
    call      ExitProcess
    end     begin
    Задание для самостоятельной работы. Составить программу для второго алгоритма и сравнить их по вычислительной сложности.

    Обзор системных вызовов для организации ввода-вывода


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


    Обзор функций WINDOWS API для работы с файлами


    Файл – это объект операционной системы. Это значит, что ОС создает для каждого файла таблицу, куда заносит информацию о файле, включающую в себя имя файла, его местоположение на диске, текущее положение указателя, режимы открытия и т.д.
    Для создания записи о файле используется функция CreateFile:
    HANDLE CreateFile(
        LPCTSTR lpFileName,       // Имя файла (с нулевым завершителем)
        DWORD dwDesiredAccess,           // Режим доступа к файлу (read-write)
        DWORD dwShareMode,    // Возможность share
        LPSECURITY_ATTRIBUTES lpSecurityAttributes,           // атрибуты безопасности
        DWORD dwCreationDistribution,  // Режимы создания
        DWORD dwFlagsAndAttributes,    // Атрибуты файла
        HANDLE hTemplateFile    // Дескриптор файла с атрибутами копирования
       );      
    Параметр dwDesiredAccess может принимать значения.
    GENERIC_READ          Разрешено чтение.
    GENERIC_WRITE         Разрешена запись. Может задаваться совместно с первым.
    (см. файл WINNT.H)
    #define GENERIC_READ                  (0x80000000L)
    #define GENERIC_WRITE                 (0x40000000L)
    #define GENERIC_EXECUTE          (0x20000000L)
    #define GENERIC_ALL                     (0x10000000L)
    DwShareMode
    -возможность совместного использования. Если параметр равен 0, файл нельзя совместно использовать.
    Допустимые значения параметров:
    FILE_SHARE_DELETE         Windows NT only: Разрешается только тогда, когда файл удаляется.
    FILE_SHARE_READ    Разрешается только для чтения.
    FILE_SHARE_WRITE   Разрешается только для записи.
    LpSecurityAttributes – указатель на структуру SECURITY_ATTRIBUTES с целью задания возможности наследования файла процессом – потомком. Если параметр равен NULL, дескриптор файла не может наследоваться.
    Windows NT: lpSecurityDescriptor определяет дескриптор безопасности для дескриптора. Если он равен NULL, дескриптор получает параметры безопасности, принятые по умолчанию, если файловая система поддерживает такие атрибуты. Для Windows 95 эта структура игнорируется.

    Функция ReadFile читает данные из файла.
    BOOL ReadFile(
        HANDLE hFile,      // Дескриптор файла
        LPVOID lpBuffer,   // адрес буфера
        DWORD nNumberOfBytesToRead,           // количество читаемых байтов
        LPDWORD lpNumberOfBytesRead,          // адрес количества прочитанных байтов
        LPOVERLAPPED lpOverlapped   // адрес тсруктуры для данных
       );      
     
    Если возвращается True, а количество прочитанных байтов равно 0, то это прочитан конец файла.
    BOOL WriteFile(
        HANDLE hFile,      // Дескриптор файла
        LPCVOID lpBuffer,            // адрес буфера
        DWORD nNumberOfBytesToWrite,           // количество записанных байтов
        LPDWORD lpNumberOfBytesWritten,       // указатель на количество записанных байтов
        LPOVERLAPPED lpOverlapped   // Указатель на структуру для асинхронного использования файла
       );      
    Пример 1. Создать файл для записи и записать туда заданную строку.
    Ideal
    p586
    model              flat
    extrn ExitProcess:proc
    extrn CreateFileA:proc
    extrn    CloseHandle:proc
    extrn ReadFile:proc
    extrn    WriteFile:proc
    extrn CloseHandle:proc
    extrn MessageBoxA:proc
    include "win.inc"
    dataseg
    my       db        '  -  25', 0
    val       dd        ?
    d1        dd        ?
    d2        dd        ?
    n1        db        'a.txt', 0
    n2        db        'b.txt', 0
    msg      db        'Error', 0
    s1         dd        ?
    codeseg
    begin:
    push 0 0 CREATE_ALWAYS 0 0
    push GENERIC_WRITE
    push     offset n1
                 
    call      CreateFileA
    cmp         eax, INVALID_HANDLE_VALUE
    jne       short m1
    push     MB_OK offset msg offset n1 0
    call      MessageBoxA
    jmp      short m2
    m1:
    mov     [d1], eax
    mov     eax, 0
    for1:
    mov     bl, [my+eax]
    test       bl, bl
    je         short break
    inc       eax
    jmp      for1
    break:
    push     0 offset s1 eax offset my [d1]
    call      WriteFile
    test       eax, eax
    jne       short m4
    push     MB_OK offset msg offset n1 0


    call      MessageBoxA
    jmp      short m2
    m4:
    push     [d1]
    call      CloseHandle
    cmp     eax, INVALID_HANDLE_VALUE
    jne       short m3
    push     MB_OK offset msg offset n1 0
    call      MessageBoxA
    jmp      short m2
    m3:
    m2:
    call      ExitProcess
    end     begin
    Пример 2. Составить функцию для копирования файлов с заданными именами.
    Ideal
    p586
    model              flat
    extrn ExitProcess:proc
    extrn CreateFileA:proc
    extrn CloseHandle:proc
    extrn ReadFile:proc
    extrn    WriteFile:proc
    extrn CloseHandle:proc
    extrn MessageBoxA:proc
    include "win.inc"
    dataseg
    my       db        '  -  25', 0
    val       dd        ?
    n1        db        'io6.exe', 0
    n2        db        'proba.txt', 0
    msg      db        'Error', 0
    s1         dd        ?
    codeseg
    proc     FileCopy
    public  FileCopy
    arg       nin:dword, nout:dword
    local     buffer:byte:512, d1:dword, d2:dword, count:dword=s
    push     ebp
    mov     ebp, esp
    sub       esp, s
    push    eax
    push     0 0 OPEN_EXISTING 0 0 GENERIC_READ [nin]
    call       CreateFileA
    cmp     eax, INVALID_HANDLE_VALUE
    jne       short @@m1
    push     MB_OK offset msg [nin] 0
    call       MessageBoxA
    jmp      @@m3
    @@m1:
    mov     [d1], eax
    push     0 0 CREATE_ALWAYS 0 0 GENERIC_WRITE [nout]
    call       CreateFileA 
    cmp     eax, INVALID_HANDLE_VALUE
    jne       short @@m2
    push     MB_OK offset msg [nout] 0
    call       MessageBoxA
    jmp      @@m3
    @@m2:
    mov     [d2], eax
    for1:
    push 0
    lea        eax, [count]
    push     eax 512
    lea        eax, [buffer]
    push     eax [d1]
    call       ReadFile
    test      eax, eax
    jne       short @@m4
    push     MB_OK offset msg [nin] 0
    call       MessageBoxA
    jmp      short @@m3
    @@m4:
    mov     eax, [count]
    test      eax, eax
    je         short break
    push     0
    lea        eax, [count]
    push     eax [count]
    lea        eax, [buffer]
    push     eax [d2]
    call       WriteFile
    test      eax, eax
    jne       short @@m5
    push     MB_OK offset msg [nout] 0
    call       MessageBoxA
    jmp      short @@m3
    @@m5:
    mov     eax, [count]
    cmp     eax, 512
    jb         short break
    jmp      for1   
    break:
    push     [d1]
    call       CloseHandle
    push     [d2]
    call       CloseHandle
    @@m3:
    pop eax
    mov     esp, ebp
    pop      ebp
    ret        8
    endp
    begin:
    push     offset n2 offset n1
    call       FileCopy
    call       ExitProcess
    end     begin

    Удаление файлов


    BOOL DeleteFile(
        LPCTSTR lpFileName        // pointer to name of file to delete 
       );      
    lpFileName – имя файла – строка с нулевым завершителем
    Windows 95: удаляет даже открытый файл. Для предотвращения потери данных файл надо сначала закрыть.
    Windows NT: Не удаляет открытых файлов.


    Копирование или перемещение файлов


    До копирования файл должен быть закрыт или открыт только для чтения. Для этого используются функции  CopyFile или CopyFileEx.
    Для перемещения файла он должен быть закрыт. Для этого используются функции MoveFile и MoveFileEx, которые копируют файл в новый и удаляют старый.
    Копирование существующего файла в новый.
    BOOL CopyFile(
        LPCTSTR lpExistingFileName,      // Имя существующего файла
        LPCTSTR lpNewFileName,            // Имя результата
        BOOL bFailIfExists            // Флаги для определения действий для //существующего файла
       );      
    bFailIfExists= TRUE
    и новый файл уже существует, то ошибка;
     bFailIfExists= FALSE и новый файл уже существует, то файл пересоздается.
    Замечание:
    Атрибуты безопасности не копируются в результирующий файл.
    Файловые атрибуты (FILE_ATTRIBUTE_*) копируются, например, если исходный файл был FILE_ATTRIBUTE_READONLY, то результирующий файл будет тоже только для чтения
    Функция MoveFile переименовывает существующий файл или каталог, переносит, если надо с устройства на устройство и, если это каталог, то переносит все вложенные подкаталоги.
    BOOL MoveFile(
        LPCTSTR lpExistingFileName,      // Имя существующего файла или каталога
        LPCTSTR lpNewFileName             // Новое имя файла или каталога
       );      
     
    lpExistingFileName – имя существующего файла или каталога;
    lpNewFileName – имя нового файла или каталога. Новое имя не должно существовать. Новое имя файла может соответствовать устройству с другой файловой системой. Новый каталог должен быть на том же устройстве.
    Рекомендуем выполнить копирование (перемещение) файлов, используя стандартные функции WIN API и функции языка СPP и сравнить их по производительности!


    Блокирование участков файлов


    Так как ОС разрешает одновременную работу с файлом нескольких приложений, может возникнуть необходимость блокирования участка файла на некоторый промежуток времени.
    Функции LockFile и LockFileEx блокируют заданный участок файла. При обращении к заблокированной области для чтения или записи – всегда ошибка..
    Функция LockFileEx даже позволяет указать, особые свойства блокирования. (exclusive)
    блокирование запрещает полный доступ всем процессам, в том числе и чтение, и запись. (shared) блокирование запрещает только запись. Это позволяет определить в файле область только для чтения.
    Для разблоктрования области используются функции UnlockFile
    или UnlockFileEx. Приложение должно разблокировать все области файла перед его закрытием
    Функция LockFileEx блокирует участок окрытого файла, задавая shared или exclusive доступ.
    BOOL LockFileEx(
        HANDLE hFile,      // Дескриптор файла
        DWORD dwFlags,  // Флаги
        DWORD dwReserved,        // Резерв, 0
        DWORD nNumberOfBytesToLockLow,     // младшие 32 бита длины //области блокирования
        DWORD nNumberOfBytesToLockHigh,    // старшие 32 бита длины
    // области блокирования
        LPOVERLAPPED lpOverlapped   // Адрес стрктуры с оределением //области блокирования
       );      
     
    Значения флагов:
    LOCKFILE_FAIL_IMMEDIATELY  Если это значение установлено и немедленно нельзя заблокировать заданный участок – сразу ошибка, иначе ждет возможности.
    LOCKFILE_EXCLUSIVE_LOCK     Блокирование типа exclusive. Иначе типа shared.
     
    Структура содержит значение смещения для блокируемого участка (поля Offset, OffsetHigh). 
    typedef struct _OVERLAPPED { // o 
        DWORD  Internal;
        DWORD  InternalHigh;
        DWORD  Offset;
        DWORD  OffsetHigh;
        HANDLE hEvent;
    } OVERLAPPED;
    Функция UnlockFileEx разблокирует заблокированную область открытого файла.
    BOOL UnlockFileEx(
        HANDLE hFile,      // Дескриптор
        DWORD dwReserved,        // Резерв, 0
        DWORD nNumberOfBytesToUnlockLow, // Младшие 32 бита
        DWORD nNumberOfBytesToUnlockHigh,            // Старшие 32-бита
        LPOVERLAPPED lpOverlapped   // Адрес структуры со смещением
       );      


    Функция FindFirstFile


    HANDLE FindFirstFile(
        LPCTSTR lpFileName,       // Имя образца для поиска
        LPWIN32_FIND_DATA lpFindFileData   // Указатель на структуру с результатом
       );      
     
    Windows 95: Образец для поиска может содержать символы (* и ?). Строка должна быть с нулевым завершителем и по длине не превосходить MAX_PATH
    символов.
    Windows NT: Ограничение на длину строки снимается, т.к. есть возможность использовать широкую версию (W) функции FindFirstFile. Символы "\\?\" говорят о возможности использования пути длиннее MAX_PATH. Она также работает с именами, заданными в UNICODE (UNC). Символы "\\?\" игнорируются как часть каталога. Например, путь "\\?\C:\myworld\private" интерпретируется как "C:\myworld\private", а "\\?\UNC\bill_g_1\hotstuff\coolapps"
    интерпретируется как
    "\\bill_g_1\hotstuff\coolapps".
    LpFindFileData - Структура WIN32_FIND_DATA.
    typedef struct _WIN32_FIND_DATA { 
        DWORD dwFileAttributes; // Атрибуты файла
        FILETIME ftCreationTime; // Время создания
        FILETIME ftLastAccessTime; // Время последнего доступа
        FILETIME ftLastWriteTime; // Время последней записи
        DWORD    nFileSizeHigh; //Размер файла – старшая часть
        DWORD    nFileSizeLow; //Размер файла  - младшая часть
        DWORD    dwReserved0; // Резерв
        DWORD    dwReserved1; // Резерв
        TCHAR    cFileName[ MAX/_PATH ]; // Имя файла
        TCHAR    cAlternateFileName[ 14 ];
    } WIN32_FIND_DATA;
     
    dwFileAttributes - Значение атрибутов, определяется битами:
    FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED,
    FILE_ATTRIBUTE_DIRECTORY     , FILE_ATTRIBUTE_HIDDEN        
    FILE_ATTRIBUTE_NORMAL         
    FILE_ATTRIBUTE_OFFLINE- данные из фала сразу же недоступны, они могут быть пока в памяти.
    FILE_ATTRIBUTE_READONLY,
    FILE_ATTRIBUTE_SYSTEM файл является частью ОС или используется только ею.
    FILE_ATTRIBUTE_TEMPORARY- временный файл-используется для временного хранения. Приложение должно писать в него, только если это абсолютно необходимо  . Большинство данных остается в памяти без сброса их на диск, т.к. файл очень скоро удаляется.


    Функция FindNextFile


    Используется для продолжения поиска по образцу, определенному функцией FindFirstFile.
    BOOL FindNextFile(
        HANDLE hFindFile,          // Дескриптор для поиска
        LPWIN32_FIND_DATA lpFindFileData   // Указатель на структуру с //данными о файле
       );      
     
    Для определения причины ошибки – используется функция GetLastError, которая возвращает ERROR_NO_MORE_FILES, если файлов больше нет.


    Функция FindClose


    Завершает  поиск, начатый функцией FindFirstFile и продолженный FindNextFile.
    BOOL FindClose(
        HANDLE hFindFile           // Дескриптор поиска
       );      
     
    Пример. Вывести текущий каталог.
    Ideal
    p586
    model              flat
    extrn ExitProcess:proc
    extrn FindFirstFileA:proc
    extrn FindNextFileA :proc
    extrn FindClose    :proc
    extrn MessageBoxA:proc
    extrn puts:proc
    include win.inc
    dataseg
    FileName        db        '*.*', 0
    FindFileData WIN32_FIND_DATA<>
    d1        dd        ?
    msg      db        'Error!!!', 0
    tit         db        '',0
    eol       db        13,10,0
    codeseg
    begin:
    lea       eax, [ FindFileData]
    push     eax
    push     offset FileName
    call    FindFirstFileA
    cmp     eax, INVALID_HANDLE_VALUE
    je         short    error
    mov     [d1], eax
    lea       ebx, [FindFileData.cFileName]
    push     ebx
    call      puts
    push    offset eol
    call      puts
    for1:
    lea       ebx,    [FindFileData]
    push     ebx
    push     [d1]
    call      FindNextFileA
    test       eax, eax
    je         short break;
    lea ebx, [FindFileData.cFileName]
    push     ebx
    call      puts
    push    offset eol
    call      puts
    jmp      for1
    break:
    push     [d1]
    call    FindClose
    jmp      short ok
    error:
    ok:
    call      ExitProcess
    end     begin
    Файл вставки (win.inc)
    MB_OK                                   equ      0
    STD_INPUT_HANDLE equ              -10
    STD_OUTPUT_HANDLE equ 11
    STD_ERROR_HANDLE equ -12
    CREATE_NEW           equ                  1
    CREATE_ALWAYS    equ                  2
    OPEN_EXISTING      equ                  3
    OPEN_ALWAYS         equ                  4
    TRUNCATE_EXISTING equ                         5
    GENERIC_READ equ    80000000h
    GENERIC_WRITE equ  40000000h
    GENERIC_EXECUTE equ 20000000h
    GENERIC_ALL          equ   10000000h
    INVALID_HANDLE_VALUE            equ (-1)
    ideal
    macro  ShowMessage  str1, tit, button
    push     button
    push     tit
    push     str1
    push     0
    call      MessageBoxA
    endm
    macro skip address

    localааа l1, l2
    ifidn ,

    regаааааа equааааа esi
    else
    regаааааа equааааа edi
    endif
    pushаааа reg
    movаааа reg, address
    l2:
    cmpаааа [byte ptr reg], ' '
    jneаааааа l1
    incаааааа reg
    jmpааааа l2
    l1:
    movаааа address, reg
    popааааа reg
    endm
    MAX_PATHааа equааааа 260
    masm
    WIN32_FIND_DATA struc
    dwFileAttributes ddаааа ?
    ftCreationTimeааааааааааа ddааааааа ?,?
    ftLastAccessTime ddааа ?,?
    ftLastWriteTimeа ddаааа ?,?
    nFileSizeHighа ddааааааа ?
    nFileSizeLowаа ddааааааа ?
    dwResааааааааааааа ddааааааа 0,0
    cFileName dbа (MAX_PATH) dup (?)
    cAlternateFileName db 14 dup (?)
    ends
    +сЁрЄшЄх тэшьрэшх, ўЄю фы  ЁрсюЄv ёю ёЄЁєъЄєЁющ яхЁхъы¦ўхэ Ёхцшь ЄЁрэёы Ўшш.

    Функции для поиска файлов


    Поиск можно выполнять по заданной маске с помощью функций FindFirstFile, FindFirstFileEx, FindNextFile, и FindClose. Образец может включать в себя символы ?, *.
    Функции FindFirstFile и FindFirstFileEx создают дескрипторы, котрые используются функцией FindNextFile. Все функции возвращают информацию о найденном файле, которая включает в себя имя файла, его размер, атрибуты и время.
    Функция FindClose разрушает дескрипторы, созданные функциями FindFirstFile и FindFirstFileEx.
    Приложение может также искать каталог(Функция SearchPath)


    Создание временных файлов


    Для создания временных файлов используется функция GetTempPath, определяющая каталог, в котором создается временный файл
    DWORD GetTempPath(
        DWORD nBufferLength,     // размер (в символах) буфера для имени //каталога
        LPTSTR lpBuffer     // адрес буфера для каталога
       );      
     
    Возвращаемое значение
    При успешном завершении – количество символов в буфере, куда записан каталог с нулевым завершителем. Если возвращаемое значение больше, чем задано, фактически возвращается требуемое значение.
    Ошибка – 0.
    Замечание.
    Каталог для временных файлов формируется так:
    1.       Каталог определяется переменной среды TMP.
    2.       Каталог определяется переменной среды TEMP, если TMP не задана.
    3.       Текущий каталог, если TMP и  TEMP не заданы.
     Для формирования имени временного файла используется функция GetTempFileName.
    Имя файла является объединением каталога для временного файла и строки – префикса, формируемой из заданного целого в 16-ой системе счисления. Расширение файла всегда TMP.
    Заметим, что функция создает имя файла, но не сам файл. Если задан 0 в качестве целого, функция создает уникальное имя файла.
    UINT GetTempFileName(
        LPCTSTR lpPathName,      // Имя каталога для временного файла
        LPCTSTR lpPrefixString,    // Строка с префиксом
        UINT uUnique,       // Число, которое используется для создания имени //файла
        LPTSTR lpTempFileName // Адрес буфера для имени файла
       );      
     
    lpPathName – указатель на строку с нулевым завершителем. Строка должна содержать символы в кодировке ANSI. Обычно задается (.), т.е. текущий каталог или результат работы функции GetTempPath. Если этот параметр равен NULL, функция завершается с ошибкой.
    lpPrefixString- Указатель на строку с нулевым завершителем. Функция использует первые 3 символа этой строки как префикс имени файла. Символы необходимо задавать в кодировке ANSI.
    uUnique – беззнаковое целое, которое функция преобразует в 16-ую строку  и использует при создании имени файла. Если uUnique != 0, функция добавляет эту строку к префиксу для формирования имени. В этом случае функция не создает заданный файл и не проверяет его имя на уникальность. Если uUnique = 0, функция формирует 16-ую строку по текущему системному времени. В этом случае формируются новые значения (++) до тех пор, пока не получим уникальное имя файла, и затем она создает файл в заданном каталоге, а потом его закрывает.
    lpTempFileName – сюда заносится сформированное имя файла в кодировке ANSI.
    Буфер должен быть достаточной длины (MAX_PATH).
    Возвращаемое значение
    Успех – функция возвращает уникальное число, которое использовалось при формировании имени файла. Если uUnique не равен 0, то возвращается это значение.
    При ошибке функция возвращает 0 ( GetLastError)
    Таким образом формат имени:
    path\preuuuu.TMP
    где
    path   путь;
    pre     первые 3 символа префикса
    uuuu  16-ое значение uUnique
     
    Когда Windows перезагружается, временные файлы, созданные этой функцией, автоматически не удаляются.  Для преобразования имени файла из формата ANSI в Windows строку, необходимо использовать функцию CreateFile для создания временного файла.


    Дополнительные функции для работы с файлами


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


    Позиционирование файла


    Для установки требуемой позиции в файле используется функция SetFilePointerEx:
    BOOL SetFilePointerEx(  HANDLE hFile,                    // handle to file  LARGE_INTEGER liDistanceToMove,  // bytes to move pointer  PLARGE_INTEGER lpNewFilePointer, // new file pointer  DWORD dwMoveMethod               // starting point);
    hFile  – дескриптор открытого файла, который создан в режиме GENERIC_READ или GENERIC_WRITE.
    liDistanceToMove – задает смещение в файле, на которое необходимо сместиться;
    lpNewFilePointer- переменная, куда записывается новое значение указателя;
    dwMoveMethod –определяет, относительно чего смещать. Может принимать значения:
    FILE_BEGIN – смещать относительно начала файла;
    FILE_CURRENT – смещать относительно ьекущего указателя в файле;
    FILE_END – смещать относительно конца файла.
    Примеры.
    Пример 1. Составить функцию для определения размера файла.


    Особенности процессоров с MMX технологией с точки зрения программиста


    Для процессоров MMX обеспечивается полная совместимость с предыдущими процессорами и операционными системами. Это означает, что программы, написанные для обычного PENTIUM будут нормально работать для PENTIUM MMX.
    Добавлен новый тип данных размером 64 бита.
    Для данных можно использовать регистры FPU, которые в этом случае обозначаются MM0..MM7, при этом MM0 соответствует ST0, MM1®ST1, ¼ независимо от вершины стека.
    Обеспечено параллельное выполнение операций для частей 64 битного данного размером 1 байт (8 частей), 2 байта (4 части), 4 байта (2 части). Такая технология называется SIMD (Single Instruction Multiple Data)- одна команда для множества данных. Это делает более эффективными операции для:
    * видео;
    * графики;
    * обработки зображений;
    * обработки аудио информации;
    * скоростной синтез и сжатие;
    * телефония и т.д.
    Используется 8 регистров MMX длиной 64 бита. Эти регистры для pentium, pentium II совмещены с регистрами FPU, поэтому нельзя одновременно  использовать команды FPU и команды MMX. Для pentium Ø èñïîëüçóþòñÿ äëÿ îáåèõ öåëåé ðàçíûå ðåãèñòðû.
    Используется 2 типа арифметик:
    арифметика с насыщением (saturating) - если результат превосходит предельное значение, он считается равным предельному (цвет не может быть белее белого и чернее черного);
    обычная арифметика с отбрасыванием переносов.


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


    Реализованы операции сложения, вычитания и умножения. Операции сложения и вычитания выполняются для 1, 2, 4, 8 байтов. Для команды умножения используется режим умножения 4-х пар знаковых чисел по 16 бит каждое. В результате умножения формируются младшие (команда PMULLW) и старшие 64 бита результата (PMULHW). Для использования этой команды для чисел многократной точности требуется предварительное преобразование числа таким образом, чтобы биты 15 и 31 были всегда 0. Вопросы эффективности умножения длинных чисел с помощью команд MMX требует дополнительного исследования.
    Наряду с элементарными арифметическими операциями используется операция умножения со сложением (PMADDWD). Сначала перемножаются соответствующие 16-битные элементы, в результате получаем четыре   32 битных элемента P0, P1, P2, P3 (знаковое умножение), а затем вычисляется два 32-битных элемента R0 = P0 + P1; R1 = P2+P3. Все операции выполняются с учетом знака.


    Команды сравнения


    Общий вид команды:
    PCMP<Условие>, например
    PCMPGT - проверка на больше;
    PCMPEQ - проверка на равно;
    Условие задается как в выражениях.
    В результате выполнения команды в первый операнд записывается 1, если условие истинно и 0 в противном случае. Команда, как и остальные команды, может одновременно выполняться для нескольких элементов.


    Команды преобразования


    Предназначены для изменения длины числа в сторону уменьшения (усечение старших разрядов или насыщения), увеличения (расширения знаковым разрядом), например
    PACKSS - упаковка со знаковым насыщением;
    PUNPCKH - расширение данного, буква H в конце означает, что формируется старшая часть результатов;
    PUNPCKL - тоже для младшей части.


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


    Все логические команды используются для 64 битного числа (почему?).
    Кроме традиционных команд (PAND, POR, PXOR, PNOT ) есть команда PANDN (AND + NOT).


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


    Логический сдвиг выполняется для 2, 4 и 8 байтовых данных.
    Команды:
    PSSL  - сдвиг влево;
    PSRL - сдвиг вправо.
    Арифметический сдвиг выполняется для 2, 4  байтовых данных.
    Команда:
    PSRA  - сдвиг вправо;


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


    Используются для пересылки 32-битных и 64 битных данных. Выполняют пересылку между памятью и MMX регистром, а также между MMX регистрами. 32-битный вариант возможно использовать для пересылки между регистром общего назначения и MMX регистром.
    MOVD - пересылка 32-битных данных;
    MOVQ - пересылка 64 битных данных.


    Управление режимами работы процессора


    Напоминаем, что одновременное использование команд MMX и команд FPU не допускается для процессоров, в которых команды MMX и FPU физически используют одни и те же регистры[20]. Вначале автоматически инициализируется режим использования FPU. Первая команда, относящаяся к классу команд MMX переключает процессор в режим использования MMX.  Для переключения режима MMХ в режим FPU используется команда EMMS. Команда без операндов. В этом случае все регистры с плавающей точкой считаются свободными. Рекомендуется эту команду писать всегда перед выходом из функции, которая использовала команды MMX[21].


    Обзор команд для MMX


    Команды делятся на:
    * команды пересылки;
    * арифметические команды;
    * команды сравнения;
    * команды преобразования;
    * логические команды;
    * команда для переключения режима MMX - FPU
    * команды сдвига.
    Общий вид кода команды:
    P<код>{U|S}[S]{B|W|D|Q},
    где:
    P - признак команды для MMX (PACK - упакованный);
    <код> - код операции, например, MOV, ADD,...
    U|S - Беззнаковое/знаковое
    S - с насыщением
    B|W|D|Q - типы данных
    Примеры команд: PADDSB, PSUBUSW.
    Требования к операндам:
    Первый операнд (SRC) - MMX регистр или память.
    Исключение: Команда PMOVD может содержать регистр общего назначения.
    Второй операнд (DEST) - MMX регистр.


    Использование команд MMX в приложениях


    Для создания мобильных приложений, которые могут исполняться для процессоров, поддерживающих технологию MMX и не поддерживающих ее рекомендуется создавать 2 варианта кода и выбирать оптимальный программным путем.
    Для определения возможности использования команд MMX для данного процессора используется команда CPUID, EAX = 1. Если технология MMX поддерживается, бит 23 в регистре EDX установлен в 1. Пример кода для проверки:

    mov   eax, 1
    cpuid
    test    edx, 00800000H
    jnz     short yes;  MMX поддерживается

    При создании функций. Использующих команды MMX допускается передача параметров через регистры MMX. В настоящее время нет соглашения для передачи параметров через регистры MMX в языке С. Поэтому функции, принимающие параметры в регистрах  MMX и соответствующий вызов необходимо писать на ассемблере или делать ассемблерные вставки.
    Возврат значения:
    * Если значений много - записываются в структуру, а адрес структуры - в регистр EAX;
    * В регистре MMX, но тогда в конце функции нельзя использовать команду EMMS, а это плохо!
    Рассмотрим более подробно операции процессора при переключении в режим MMX:
    * В поле экспоненты всех MMX - регистров записывается 1 во все биты (16 бит);
    * В поле тегов записывается 00, что соответствует занятости всех регистров;
    * Вершина стека устанавливается на физически первый регистр.
    В таблице представлено влияние команд FPU/MMX на регистры
    Для того, чтобы частое переключение режимов меньше сказывалось на производительности процессора рекомендуется:
    *  в функции (цикле) использовать команды одного типа;
    *  не использовать содержимое регистров, сформированное командами другого типа;
    *  как только команды MMX не требуются выполнить переключение режима (команда EMMS);
    *  перед выходом из функции, использующей FPU очищать стек FPU.


    Операционные системы, сохраняющие состояние MMX/FPU


    Операционные системы могут сохранять:
    * полное состояние для FPU;
    * сохраняет состояние по специальному запросу;
    * сохраняет только частично состояние.
    Вначале для всех задач предполагается, что MMX/FPU не требуется CR0.TS=1.
    Если встречается команда FPU/MMX, вызывается обработчик int 7 (устройство не доступно), обработчик входит в состав OS, и  выполняет:
    * выделение памяти для состояния задачи;
    * формируется переменная, определяющая текущий режим;
    * включается требуемый режим.
    Бит CR0.TS устанавливается в 0, что означает, что необходимо сохранять – восстанавливать среду.
    Заметим, что само состояние сохраняется - восстанавливается пользователем с помощью команд FSAVE, FRSTOR  для обоих режимов.
    Таким образом, если переключаемая задача имеет CR0.TS=0, OS определяет установленный режим и состояние регистров, включает требуемый режим, если CR0.TS=1, это означает, что задача пока не использовала команд FPU/MMX, значит восстанавливать нечего.


    Особенности использования режима MMX для многозадачных OS


    OS по характеру обработки режима MMX можно разделить на 2 класса:
    * Cooperative OS - не сохраняет состояние MMX/FPU, поэтому само приложение должно само заботиться о требуемом состоянии.
    * Preemptive OS - операционная система при переключении задач сохраняет текущее состояние.
    В первом случае приложение может отследить момент переключения задач и сохраняет требуемые данные, если это требуется.
    Во втором случае переключение между задачами может произойти в любое время, поэтому проблему сохранения/восстановления должна решать ОS.


    Отладка программ с MMX командами


    При отладке программ необходимо помнить, что нет режима эмуляции как для FPU. Если установлен режим эмуляции (CR0.EM=1) и выполняется команда MMX, то   формируется исключительная ситуация (int 6). Эта же ситуация возникает, если процессор не поддерживает MMX команд, а в программе встретилась эта команда.


    ОСОБЕННОСТИ ПРОГРАММИРОВАНИЯ ДЛЯ MMX


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


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


    Основной характеристикой процессора является его частота. Частота определяет время выполнения одного такта, которое обычно соответствует времени выполнения одной простой инструкции. Например, если частота процессора 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    *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 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    For (j=0; 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       For (j=0; j }
    ; P=1;
    mov     eax, 1
    ;For (i=0; i mov     ecx, [n]
    shl        ecx, 16
    mov     ebx, offset a
    ;      For (j=0; 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 ;For (s=i=0; 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 loop     fori      
    mov     [s], eax
    Вариант 2. Индексная адресация
    ;For (s=i=0; 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 аа 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 аа 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:

    Немонотонное изменение параметра цикла


    Пусть индекс изменяется по произвольному закону. В этом случае сначала вычисляется значение индекса, а затем выполняются действия для элемента.
    Пример 1. Составить программу для нахождения заданного элемента в массиве целых упорядоченных чисел.
    nom=-1;
    i=0; j=n-1;
    while (i<=j){
       k=(i+i)/2;
       if (x[k]==r){
          nom = k;
       break;
     }
     if (x[k] }
    ;nom=-1;
    mov     eax, -1
    ;i=0; j=n-1;
    mov     ebx, 0
    mov     ecx, [n]
    dec       ecx
    ;while (i<=j){
    cmp     ebx, ecx
    jg         short m1
    ;   k=(i+i)/2;
    mm9:
    mov     edx, ebx
    add      edx, ecx
    shr       edx, 1
    ;   if (x[k]==r){
    mov     esi, [x+edx*4]
    cmp     esi, [r]
    jne       mm6
    ;nom = k;
    mov     [nom], edx
    ;break;
    jmp      short mm7
    ;}
     ;if (x[k] mm6:
    cmp     esi, [r]
    jge       short mm8
    mov     ebx, edx
    inc       ebx
    jmp short mm9
    mm8:
    mov     ecx, edx
    dec       ecx
    jmp short mm9
    ;}
    Пример 2. В заданной строке символов переставить символы в порядке, заданном числовым массивом
    for (i=0; str1[i]; i++){
       ch = str1[i];
       n = nom[i];
       str2[n] = ch;
    }
    str2[i] = 0;
    str1      db        ‘abcdef’, 0
    nom     dd        5, 1, 0, 2, 4, 3
    str2      db        (nom - str1) dup (?)
    ...
    ;for (i=0; str1[i]; i++){
    mov     eax, 0
    ;ch = str1[i];
    m2:
    mov     bl, [str1+eax]
    test       bl, bl
    je         short m1
    ;n = nom[i];
    mov     ecx, [nom+eax]
    ;n = nom[i];
    mov     [str2+ecx], bl
    inc       eax
    jmp      m2
    m1:


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


    Команды позволяют обрабатывать массив чисел длиной 1, 2, 4 байтов, начиная с начала или конца массива. При этом автоматически изменяется адрес элемента массива на длину элемента массива. Для массива можно выполнить следующие операции:
    копирование массива в другой массив:
    сравнение двух массивов;
    заполнение массива заданным значением или последовательным чтением элементов массива;
    поиск заданного элемента массива.
    Для определения направления поиска устанавливается флаг d регистра флагов (бит №  10).
    Адрес исходного массива задается в регистре ESI, а результирующего - в регистре EDI.
    Количество элементов массива задается в регистре ECX.
    Команды :
    MOVS{B|W|D}          - do{*(edi++) = *(esi++) while (ecx--);}
    LODS{B|W|D}           {AL|AX|EAX} = *(esi++)
    STOS{B|W|D}            *(edi++) = {AL|AX|EAX}
    CMPS{B|W|D}           сравнение *(esi++) и *(edi++)
    SCAS{B|W|D}            do{ if {AL|AX|EAX} = (!=) ) *(edi++) break; while (ecx--);
    В таблице 7.1 представлена сводная таблица команд для работы с блоками
    Таблица 7.1. Сводная таблица команд для работы с блоками

    Код
    Выполняемые действия
    Подготовка команды
    Используемый префикс
    MOVS
    dest = src
    cld (std);
    esi = &src[10]
    edi = & dest
    ecx = number
    REP
    LODS
    {al, ax, eax} = *esi++
    cld (std);
    esi = &src
    Префикс не используется
    STOS
    *edi++ = {al, ax, eax}
    cld (std);
    edi = &dest
    ecx = number
    REP
    CMPS
    *src ++
    <>*dest++
    cld (std);
    esi = &src7
    edi = & dest
    ecx = number
    REPE, REPNE
    SCAS
    {al, ax, eax} <>
    *dest++
    EAX - что ищем,
    edi = & dest (где ищем)
    ecx = number
    REPE, REPNE

    Примеры использования команд
    Скопировать область памяти заданной длины в другую область памяти, если адрес начала первой области f1, ее размер len байт, адрес начала второй области f2. Пусть len кратно 4.
    В этой программе сначала определяется направление копирования. Если области памяти для исходного и результирующего массивов пересекаются, при этом адрес начала результирующего массива больше, чем адрес исходного, необходимо копировать, начиная с конца массива, в противном случае – с начала.

    Е
    Movаааа esi, [f1]
    Movаааа edi, [f2]
    Movаааа ecx, [len]
    Shrаааааа ecx, 2
    Jecxzааа m1
    cld
    cmpаааа esi, edi
    jaаааааааа short m2
    jeаааааааа break
    movаааа esi, eax
    decаааааа esi
    addааааа edi, [len]
    decаааааа edi
    std
    m2:
    repаааааа movsd
    break:
    cld
    ¦ЁшьхЁ 2. T чрфрээющ ёЄЁюъх ьрыхэ№ъшх ырЄшэёъшх сєътv чрьхэшЄ№ сюы№°шьш.
    Str1ааааа dbааааааа СThis is stringТ,0
    Е
    cld
    movаааа esi, offset str1
    movаааа edi, offset str1
    for1:
    lodsb
    testаааааа al, al
    jeаааааааа short break
    cmpаааа al, СaТ
    jbаааааааа notl
    cmpаааа al, СzТ
    jaаааааааа notl
    addааааа al, СAТ Ц СaТ
    notl:
    stosb
    jmp for1
    break:
    cld
    ¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы  ёЁртэхэш  фтєї фышээvї ўшёхы юфшэръютющ фышэv. ¦ЁюуЁрььр фюыцэр ЇюЁьшЁютрЄ№ Ёхчєы№ЄрЄ >0, хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую, Ёртэю 0, хёыш ўшёыр Ёртэv ш <0, хёыш яхЁтюх ўшёыю ьхэ№°х тЄюЁюую. ¦єёЄ№ ўшёыю ёюёЄюшЄ шч n 32-сшЄэvї лЎшЇЁ¬ ш чряшёvтрхЄё , эрўшэр  ё ьырф°шї ЎшЇЁ.
    Xааааааааа ddааааааа Е, Е., Е.
    Yааааааааа ddааааааа Е, Е, Е
    Nаааааааа ddааааааа (Y Ц X)/4
    Resааааа ddааааааа ?
    Е
    subаааааа eax, eax
    movаааа ecx, [N]
    leaаааааа esi , [X+ecx*4-4]
    leaаааааа edi , [Y+ecx*4-4]
    std
    repeаааа cmpsd
    jecxzааа m1
    jbаааааааа letter
    movаааа eax, 1
    jmpааааа short m1
    letter: movаааааа eax, -1
    m1:
    cld
    ааааааааа
    ¦ЁшьхЁ 4.
    T чрфрээюь ьрёёштх ўшёхы тёх эєыхтvх чэрўхэш  чрьхэшЄ№ Ц1
    While (){
    аа If (хёЄ№ эєыхтюх чэрўхэшх)
    аа ааа¦рьхэшЄ№;
    аа Else
    ааааа Break;
    }
    movаааа ecx, [n]
    cld
    subаааааа eax, eax
    movаааа edi, offset x
    for1:
    repneаа scasd
    jecxzааа break
    movаааа [dword ptr edi-4], -1
    jmpааааа for1



    Задание двухмерного массива


    Пусть необходимо задать матрицу, определенную в С:
    int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    matr     dd        1,2,3,4
                dd        5,6,7,8
                dd        9,10,11,12
    Задание адреса элемента
    Для адресации данных массива можно использовать 2 регистра индексный и базисный [3.4.3]. Для каждого индекса определяется шаг изменения адреса.
    Например, для массива:
    int x[M][N] шаг изменения адреса по второму индексу Hj равен sizeof (int) = 4, а для первого индекса Hi равен Hj * N. Если длина элемента совпадает со значением масштабного множителя (стандартная длина элемента -1, 2, 4, 8), то в индексный регистр записывается значение индекса, если не совпадает то - значение смещения.
    Для задания адреса элемента X[i][j] со стандартной длиной элемента используется запись вида [X+REG1 + REG2 * LEN], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2-
    общий регистр, содержащий значение индекса j. Для элемента массива нестандартной длины адрес задается в виде [X+REG1 + REG2], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2- общий регистр, содержащий смещение для j - ого элемента строки.
    Пример. Оттранслировать операторы:
    int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    long double numbers [][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    // Формирование i, j
    matr[i][j]=0; numbers[i][j]=0;
    matr     dd        1,2,3,4
                dd        5,6,7,8
                dd        9,10,11,12
    numbers         
    dt         1,2,3,4
                dt         5,6,7,8
                dt         9,10,11,12
    HIMATR         dd        16
    HINumbers     dd        40
    ...
    mov     eax, HIMATR
    mul      [i]
    mov     ebx, [j]
    mov     [matr+eax+ebx*4], 0
    mov     eax, HINumbers
    mul      [i]
    mov     ebx, eax; База
    mov     eax, 10
    mul      [j]
    mov     [dword ptr numbers + eax + ebx], 0
    mov     [dword ptr numbers + eax + ebx + 4], 0
    mov     [word ptr numbers + eax + ebx + 8], 0


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


    Структура программы с монотонным изменением индексов:
    Вычисление Hj , Hi
    Цикл для вычисления.
    Пример. Составить программу для вычисления сумм столбцов матрицы:
    for (j=0; j             s[j]=0;
                for (i=0; i                         s[j]+=matr[i][j]
    }
    Для оптимизации программы запишем:
    for (j=0; j             r=0;
                for (i=0; i                         r+=matr[i][j]
                s[j]=r;
    }
    ; Вычисление Hi =4*N, Hj=4
    mov     eax, [N]
    shl        eax, 2; Hi
    mov     ebx, 4; Hj
    ;for (j=0; j mov     ecx, [N]
    shl        ecx, 16
    mov     edx, 0; j
    forj:
    ;           r=0;
    mov     esi, 0
    ;           for (i=0; i mov     cx, [M]
    mov     edi, 0; i
    ;                       r+=matr[i][j]
    fori:
    add      esi, [matr + edi + edx*4]
    ; i++
    add      edi, eax
    dec       cx
    jnz        fori
    ;           s[j]=r;
    mov     [s+edx*4], esi
    ;j++
    inc       edx
    sub       ecx, 10000h
    jnz        forj
    }
    ...
    Недостатки метода:
    для каждого индекса свой регистр;
    не может быть обобщен на массивы большей размерности.


    Метод связанных индексов


    Адрес элемента в начале строки используется для формирования адреса очередного элемента для обоих индексов, т.е. фактически требуется 2 экземпляра этого адреса. Т.к. фактически используется одно и тоже значение для работы с каждым индексом метод называется методом связанных индексов. Для получения 2-х экземпляров используется стек. Формируется необходимое значение, один экземпляр которого записывается в стек, второй остается в регистре для работы со вторым индексом. При необходимости работы с первым индексом извлекается значение из стека.
    Структура программы для метода связных индексов:
    for (i=0; i<.. i++){                             mov     ecx, ...
    mov     ebx, Смещение ПОЭ[11]
                ...
    for (j=0; j<... ; j++){              fori:
                            ...                                 push     ecx
                                                                push     ebx
                                                                mov     ecx,...
                                                                forj:
                                                                [Имя массива + ebx]
    }                                               add      ebx, [Hj]
                                                                loop     forj
    }                                                           pop      ebx
                                                                add      ebx, Hi
                                                                pop      ecx
                                                                loop     fori
    Пример 1. Составить программу для нахождения сумм строк матрицы с нечетными номерами
    for (k=i=0; i    r=0;
       for (j=0; j       r+=matr[i][j]
       s[k]=r;
    }
    mov     esi, [N]
    shl        esi, 3; Шаг по i
    ;for (k=0. i=1; i sub       eax, eax; k
    mov     ecx, [M]
    shr       ecx, 1
    mov     ebx, [N]
    shl        ebx, 2; i
    fori:
    ;r=0;

    subаааааа edx, edx
    ;аа for (j=0; j pushаааа ecx
    movаааа ecx, [N]
    pushаааа ebx
    forj:
    ;ааааа r+=matr[i][j]
    addааааа edx, [matr+ebx]
    ;j++
    addааааа ebx, 4
    loopаааа forj
    ;аа s[k]=r;
    movаааа [s+eax*4], edx
    ;i++
    incаааааа eax
    popааааа ebx
    addааааа ebx, esi
    popааааа ecx
    loop fori
    ¦ЁшьхЁ 2. TюёЄртшЄ№ яЁюуЁрььє фы  яхЁхьэюцхэш  ьрЄЁшЎ
    ideal
    p586
    model flat
    extrn ExitProcess:proc
    dataseg
    matrаааааа ddааааа 1,1,1,1,1
    аааааааааа ddааааа 2,2,2,2,2
    аааааааааа ddааааа 3,3,3,3,3
    аааааааааа ddааааа 4,4,4,4,4
    Mааааааааа ddааааа 4
    Nааааааааа ddааааа 5
    Sааааааааа ddааааа 5 dup (?)
    codeseg
    begin:
    movааа esi, [N]
    shlааа esi, 3; ? г по i
    ;for (k=0. i=1; i subа eax, eax; k
    movа ecx, [M]
    shrа ecx, 1
    movа ebx, [N]
    shlа ebx, 2; i
    fori:
    ;r=0;
    subаа edx, edx
    ;аа for (j=0; j pushааа ecx
    movаааа ecx, [N]
    pushааа ebx
    forj:
    ;ааааа r+=matr[i][j]
    addааа edx, [matr+ebx]
    ;j++
    addа ebx, 4
    loopааааа forj
    ;аа s[k]=r;
    movаааааааа [S+eax*4], edx
    ;i++
    incа eax
    popа ebx
    addа ebx, esi
    popа ecx
    loop fori
    callааа ExitProcess
    endаааа begin
    ¦ръ тшфэю шч яЁюуЁрььv фы  ърцфюую шч ьрёёштют ЄЁхсєхЄё  Єюы№ъю юфшэ ЁхушёЄЁ.

    Использование 2-х мерного массива при немонотонном изменении индекса


    Пусть задана разреженная матрица, содержащая очень много нулей.
    Для упрощения задания определяются только ненулевые элементы матрицы в виде:
    x, y, value,
    где x, y - координаты ненулевого значения. Составить программу для заполнения матрицы заданными значениями.
    Для решения этой задачи потребуется задать адрес элемента массива matr[x][y], который вычисляется по формуле:
    & matr[x][y] = & matr[0][0] + x * Hx + y * Hy
    ; Задание матриц
    PackMatr                    dd        1, 3, 10
                                        dd        2, 7, 20
                                        dd        4, 4, 30
    count                           dd        3
    UnpackMatr                dd        5 dup (5 dup 0)
    hi                                 dd        5*4
    ; Заполнение матрицы UnpackMatr:
    mov     ecx, [count]
    mov     ebx, offset PackMatr
    for1:
    mov     eax, [ebx]        ;x
    mul      [hi]                  ; x * hi
    mov     edx, [ebx+4]   ;y
    mov     esi, [ebx+8]    ;value
    mov     [UnpackMatr+eax+edx*4], esi
    add      ebx, 12
    loop     for1


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


    Пример. Составить программу для реализации фрагмента программы:
    int a[3][5][4];
    for (k=1, l=0; k<4; k+=2, l++){
    r=0;
                for (i=0; i<3; i++)
                            for(j=0; j<5; j++)
                                        r+=a[i][j][k];
                s[l]=r;
    }
    ...
    dataseg
    a          dd        1,1,1,1
                dd        2,2,2,2
                dd        3,3,3,3
                dd        4,4,4,4
                dd        5,5,5,5
                dd        1,1,1,1
                dd        2,2,2,2
                dd        3,3,3,3
                dd        4,4,4,4
                dd        5,5,5,5
                dd        1,1,1,1
                dd        2,2,2,2
                dd        3,3,3,3
                dd        4,4,4,4
                dd        5,5,5,5
    s           dd        2 dup(?)
    hk        dd        4
    hj         dd        16
    hi         dd        16*5
    codeseg
    begin:
    ;for (k=1, l=0; k<4; k+=2, l++){
    mov     ecx, 4
    shr       ecx, 1
    mov     ebx,offset a
    add      ebx, 4
    mov     esi, offset s
    fork:
    ;r=0;
    sub       edi, edi
    ;for (i=0; i<3; i++)
    push     ebx
    push     ecx
    mov     ecx, 3
    ;for(j=0; j<5; j++)
    fori:
    push     ebx
    push     ecx
    mov     ecx, 3
    forj:
    ;r+=a[i][j][k];
    add      edi, [ebx]
    ;j++
    add      ebx, [hj]
    loop     forj
    ;i++
    pop      ecx
    pop      ebx
    add      ebx, [hi]
    loop     fori
    ;s[l]=r;
    mov     [esi], edi
    ;l++
    add      esi, 4
    pop      ecx
    pop      ebx
    add      ebx, 8
    loop     fork
    ;}


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


    Пусть определение массива имеет вид:
    Тип Имя [p1..q1, p2..q2, ...pn..qn], где pi..qi- минимальное и максимальное значения i-ого индекса
    Адрес элемента массива с индексами i1, i2, ...in определяется формулой[12]:
    & Имя[i1, i2, ...in] = Адресу начала массива +
    Использование метода приведенных индексов для многомерных массивов
     -
    Использование метода приведенных индексов для многомерных массивов
    , где Hi - шаг изменения адреса для i-ого индекса.
    Формулы для вычисления адреса:
    Hn = sizeof (Тип);
    Hn-1 = Hn * (qn
    - pn +1);
    ...
    H1 = H2 * (q2
    - p2 + 1).
    Формулы вычисления адреса показывают, что наиболее простой вид формула имеет при начальном значении индексов, равном 0.
    Пример1. Реализовать фрагмент программы:
    int x[3][4][5][6];
    ...
    x[i][j][k][l]=i+j+k+l;
    x          dd        (3*4*5*6) dup (?)
    i           dd        1
    j           dd        2
    k          dd        3
    l           dd        4
    hl         equ      4
    hk        equ      6*hl
    hj         equ      5*hk
    hi         equ      4*hj
    ...
    mov     eax, [i]
    imul     eax, hi
    mov     ebx, [j]
    imul     ebx, hj
    add      eax, ebx
    mov     ebx, [k]
    imul     ebx, [hk]
    add      eax, ebx
    mov     ebx, [l]
    mov     ecx, [i]
    add      ecx, [j]
    add      ecx, [k]
    add      ecx, [l]
    mov     [x+eax+ebx*4], ecx

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


    Многомерные массивы


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


    Задание типа структуры


    Общий вид задания типа:
    Struc Имя  
    поле1
    поле2
    ...
    ends [Имя]
    Полями структуры могут быть директивы определения данных, в том числе и другие структуры, т.е. допускается вложение структур.
    Пример. Определить тип для задания структуры «Возраст», содержащей день рождения в виде:
    Год(2 байта) Месяц (1 байт)          День (1 байт)
    STRUC  AGE
    day                  db        ?
    month  db        ?
    year                 dw       ?
    ends


    Выделение памяти под структуру


    Для выделения памяти используется запись вида:
    Имя поля        Тип структуры         ?  или значение полей структуры.
    Пример:
    age1    AGE    ?
    age2    AGE    ?
    Как и для стандартных типов данных, можно задавать коэффициенты повторений.
    AgeList            AGE    50 dup (?)
    Имена полей глобальны, т.е. нельзя использовать одно и то же имя для разных структур или это имя для простых переменных.


    Инициализация полей структуры


    Используется 3 способа инициализации.
    Способ 1. Значения полей задаются в задании типа структуры, например:
    STRUC  AGE 
    day                  db        1
    month              db        1
    year                 dw       1980
    ends
    Эти значения переносятся в поля структуры, если для них не используются другие способы задания.
    Способ 2. Позиционное задание полей. Значения полей задаются в порядке их следования в определении данного через запятую в угловых скобках, например
    age1    AGE    < 1,2, 1979>
    age2    AGE    < 25, 10, 1981,>
    Если значение какого-то поля переопределять не нужно, оно не записывается, но запятая ставится, например:
    age3    AGE    <25, 10,>
    В этом случае год рождения 1980, как определено в задании типа.
    Если не определяется значение последних полей, запятые можно не ставить, например:
    age4    AGE    <25 >
    Здесь месяц и год определяются из задания типа.
    Если значения полей не надо переопределять, записываются просто угловые скобки, например
    age5    AGE    < >
    Способ 3. Непозиционное задание полей. Значения полей задаются в произвольном порядке в фигурных скобках. Для каждого значения используется формат:
    Имя поля = Значение.
    Определения разделяются запятыми. Пример.
    age5    AGE    {day = 3, year = 1990, month = 2}


    Особенности инициализации строк и массивов


    Пусть полем структуры является строка длиной 10 символов. Чтобы можно было задавать начальные значения этому полю, необходимо его представление в виде:
    pole1   db        ‘          ’
    В кавычках задано требуемое количество пробелов. Если строка задана в виде 10 dup ( ), компилятор ее воспринимает как данное типа 1 байт и допускает инициализацию только одного байта. При задании числовых массивов или используются разные имена для отдельных элементов, или инициализация не допустима.


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


    Для обращения к полю по имени структуры используется запись вида:
    Имя структуры. Имя поля.
    Если адрес структуры задан в регистре, то поле структуры задается в виде [(Имя структуры reg). Имя поля].
    Пример. Задан массив с возрастами. Определить возраст самого старшего человека списка
    ideal
    p386
    model   flat
    extrn ExitProcess:proc
    dataseg
    STRUC AGE
    day db 1
    month db 1
    year dw 1980
    ends
    list  AGE < 3,10>, <1,1,1993>
          AGE  < 1,1, 1989>, <>
    count dd 4
    old AGE ?
    codeseg
    begin:
    mov eax, offset list
    mov bx, [(AGE eax).year]
    mov dl, [(AGE eax).month]
    mov dh, [(AGE eax).day]
    mov ecx, [count]
    dec ecx
    add eax, 4
    for1:
    cmp bx, [(AGE eax).year]
    jl short next
    jg write
    cmp dl, [(AGE eax).month]
    jl short next
    jg short write
    cmp dh, [(AGE eax).day]
    jle short next
    write:
    mov bx, [(AGE eax).year]
    mov dl, [(AGE eax).month]
    mov dh, [(AGE eax).day]
    next:
    add eax, 4
    loop for1
    mov [old.year], bx
    mov [old.month], dl
    mov [old.day], dh
    call ExitProcess
    end begin
    Вариант 2.
    Использовать для сравнения целиком всю запись.
    ideal
    p386
    model   flat
    extrn ExitProcess:proc
    dataseg
    STRUC AGE
    day db 1
    month db 1
    year dw 1980
    ends
    list  AGE < 3,10>, <1,1,1993>, < 1,1, 1989>, <>
    count dd 4
    old AGE ?
    codeseg
    begin:
    mov eax, offset list
    mov ebx, [ eax]
    mov ecx, [count]
    dec ecx
    for1:
    add eax, 4
    cmp ebx, [ eax]
    jle short next
    write:
    mov ebx, [ eax]
    next:
    loop for1
    mov [dword old], ebx
    call ExitProcess
    end begin
    Пример 2. Составить программу для упорядочивания списка студентов в порядке убывания среднего балла
    Способ 1. В запись включить фамилию и оценки - недостаток - размер фамилии может содержать разное число символов - надо выделять память по максимуму.
    Способ 2. Записать список фамилий. Адрес каждой фамилии -в запись. Достоинства:
    памяти, сколько требуется;
    при сортировке перемещается не строка, а ее адрес - уменьшается требуемое время.

    Lёяюы№чєхь фы  єяюЁ фюўштрэш  ьхЄюф яюфёўхЄр .
    TєЄ№ ьхЄюфр: -ы  ърцфюую ўшёыр юяЁхфхы хь ъюышўхёЄтю ўшёхы, яЁхф°хёЄтє¦•шї фрээюьє ўшёыє. ¦рЄхь ўшёыр ёЄрт Єё  эр ётюш ьхёЄр т фЁєуюь ьрёёштх.
    ; +яЁхфхыхэшх ъюышўхёЄтр яЁхф°хёЄтє¦•шї ўшёхы
    аfor (i=0; i for (i=1; i аа for (j=0; j ааааа if (x[j]>x[i]) s[j]+=1; else s[i]+=1;
    ; ¦ряшё№ ўшёхы эр ётюш ьхёЄр
    for (i=0; i аа y[s[i]]=x[i];
    ideal
    p386
    modelаа flat
    extrn ExitProcess:proc
    dataseg
    fio1ааа dbааааа 'aaaaaaaa', 0
    fio2ааа dbааааа 'bbbbbb', 0
    fio3ааа dbааааа 'cccccccccccccccc', 0
    fio4ааа dbааааа 'ddddddd', 0
    fio5ааа dbааааа 'eeeeeeeeeeeeeeeeeeee', 0
    struc аа data1
    pfioаааааа ddаа ?
    oc1ааааааа dbаа ?
    oc2ааааааа dbаа ?
    oc3ааааааа dbаа ?
    oc4ааааааа dbаа ?
    ends
    gr1ааааааа data1а < offset fio1, 2, 3, 4, 5>, < offset fio2, 3,4,5,3>
    аааааааааа data1а < offset fio3, 5, 5, 5, 5>, < offset fio4, 5,4,5,5>
    аааааааааа data1а < offset fio5, 5, 4, 4, 4>
    countааааа ddааа 5
    rааааааааа data1 5 dup (?)
    sааааааааа ddаа 5 dup(0)
    codeseg
    begin:
    ;for (i=1; i movа ecx, [count]
    decа ecx
    movаааа eax, 1
    movа esi, offset gr1
    addааааа esi, 8
    fori:
    movа bl,0
    addа bl, [(data1 esi).oc1]
    addа bl, [(data1 esi).oc2]
    addа bl, [(data1 esi).oc3]
    addа bl, [(data1 esi).oc4]
    ;аа for (j=0; j movаааа edx, 0
    movаааа edi,offset gr1
    forj:
    cmpаааа edx, eax
    jgeаааа breakj
    movаааа bh, 0
    addаааа bh, [(data1 edi).oc1]
    addаааа bh, [(data1 edi).oc2]
    addаааа bh, [(data1 edi).oc1]
    addаааа bh, [(data1 edi).oc1]
    ;ааааа if (x[j]>x[i]) s[j]+=1; else s[i]+=1;
    cmpааа bh, bl
    jgeааа short mge
    incааа [s+edx*4]
    jmpааа short nextj
    mge:
    incааа [s+eax*4]
    nextj:
    incааа edx
    addааааа edi, 8
    jmpаа forj
    ;i++
    breakj:
    incа eax
    addааааа esi, 8
    loopаааа fori
    ;for (i=0; i movа ecx, [count]
    movа eax, 0
    for2:
    ;аа y[s[i]]=x[i];
    leaаааааа esi, [ gr1 + eax* 8]
    movа ebx, [s+eax*4]
    leaаааааа edi, [r+ebx*8]
    movа edx, [(data1 esi).pfio]
    movа [(data1 edi).pfio], edx
    movа edx, [dwordа (data1 esi).oc1]
    movа [dword (data1 edi).oc1], edx
    ;i++
    incа eax
    loopаа for2
    call ExitProcess
    end begin

    Структуры


    Используются для задания полей разной длины, входящих в одну запись.


    Особенности инициализации полей объединения


    В отличие от структур можно инициализировать только элемент первого поля. Для инициализации можно использовать такие же способы, как для структуры.
    Пример. В предыдущей задаче поле оценок использовалось как 4 байта и одно 4-х байтовое слово. Задать это с помощью объединения.
    STRUC  STRUC1      
    oc1        db   ?
    oc2        db   ?
    oc3        db   ?
    oc4        db   ?
    ends
    UNION           UNION1
    st1     STRUC1     <5,5,5,5>
    st2        dd        ?
    ends
    STRUC  DATA1
    pfio       dd   ?
    oc        UNION1         ?
    ends
    Для доступа к полям в этом случае используется запись вида:
    data1.oc.st1.oc1


    Объединения


    Используются, если необходимо одно и тоже поле памяти использовать как данные разного типа. Отдельные поля объединения записываются в памяти, начиная с одного и того же адреса - это отличие от структуры.
    Общий вид объединения
    UNION Имя  
    поле1
    поле2
    ...
    ends [Имя]
    Требования и способы задания полей как для структур.


    Перечисления


    Общий вид:
    Имя типа ENUM {Имя поля [= значение], Имя поля [= значение],... }
    Перечисление используется для задания группы констант, чтобы не повторять директивы EQU или =. Имя поля является именем константы. Значение первого поля, если оно не задано, принимается равным 0, значение любого другого поля, если оно не задано, равно значению предыдущего, увеличенному на 1. Значения разных полей в списке могут повторяться.
    Пример.
    Запись COLORS ENUM {BLUE, GREEN, RED} эквивалентна определению трех констант:
    BLUE  EQU    0
    GREEN           EQU    1
    RED                EQU    2
    Поля объединений можно использовать в любом месте, где допускается использование константы.


    СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ


    1. Зубков С.М. Assembler. Для Dos, Windows и  Unix. - М.: ДМК. 1999. 640 с.
    2. Юров В.И. “Ассемблер: Учебный курс”. Питер, 1998
    3. Рихтер Джефри. «Профессиональное программирование для Windows 2000, NT», Питер, 2000
    4. Качко Е.Г. Программирование на ассемблере. Харьков, ХТУРЭ, 1997
    5. Методические указания к практическим занятиям и самостоятельной работе по курсу «Системное программирование», // Качко Е.Г., Белецкий Е.В., Мельникова Р.В. Харьков, ХТУРЭ, 1998.
    [1]
    Настоятельно рекомендуем использовать этот принцип для программ собственной разработки.
    [2]
    Это плохой стиль написания программы, но он здесь использован для демонстрации команды условного перехода
    [3]
    Точнее, автору не известны эти обозначения
    [4]
    Уточнение адреса для страничного  режима в данном курсе не рассматривается
    [5]
    Не огорчайтесь, если материал этого раздела для Вас не совсем понятен. Постепенно вы научитесь правильно использовать все способы адресации
    [6]
    Вычисление остатка от деления
    [7]
    Обычные знаки типа <, > не используются, т.к. зарезирвированы для применения в макросах
    [8]
    Предполагается, что цифры числа задаются в памяти, начиная с младших цифр, т.е. чем младше цифра, тем меньше ее адрес
    [9]
    Для решения этой задачи можно использовать формулу суммы арифметической прогрессии. Цикл используется для демонстрации методики применения циклов
    [10]
    Задаются адреса первых обрабатываемых элементов
    [11]
    ПОЭ – первый обрабатываемый элемент массива
    [12]
    Формула может быть доказана методом математической индукции.
    [13]
    Вычислите, в каком году возникнет «проблема 2000 года!»
    [14]
    Вызов может быть прямой и косвенный. При прямом вызове задается имя процедуры, при косвенном - адрес с адресом процедуры.
    [15]
    Для С++ Builder метка может быть записана внутри ассемблерного кода
    [16]
    Оптимизационные способности современных компиляторов настолько высоки, что не имеет смысла применять регистровые переменные
    [17]
    Здесь и далее в таблице многоточию соответствуют первые буквы типов параметров в порядке их следования в заголовке
    [18]
    Функции заданы для однобайтной кодировки символов.
    [19]
    Все функции WINDOWS API, которые возвращают значения типа BOOL, устанавливают его равным TRUE при благополучном завершении функции и FALSE в случае ошибки
    [20]
    Это ограничение снято для PENTIUM III.
    [21]
    Если операционная система не восстанавливает этот бит при переключении задач, рекомендуется выполнять переключение сразу после исполнения участка кода с MMX.


    

        Программирование: Языки - Технологии - Разработка