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

         

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


Пусть задан класс:

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 <y> , <eax>

r           equ      <ebx>

else     

r           equ      <eax>

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 <RES>, <eax>

reg     equ     ebx

else

reg     equ     eax

endif

push    reg

mov     reg, X1

IRP   X, <X2, X3, X4, X5>

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 <windows.h>

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 <offset text>, <offset t>, 0
call    ExitProcess
end     begin
 
Пример 2. Составить функцию gets для ввода строки с клавиатуры, которая в результате формирует строку с нулевым завершителем. Признак конца вводимой строки- символ <Enter>.
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 <di>, <address>

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 <di>, <address>
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, а дать основные особенности с целью определения необходимости использования этих возможностей процессора.



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


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.



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