Связь Паскаля с Ассемблером реализуется как многомодульная програм-
ма, где главный модуль написан на Паскале, а вспомогательный на языке Ас-
семблера. Обычно, в Ассемблерном модуле реализуется часть алгоритма прог-
раммы, которую выполнить в Паскале трудно или данная часть критична к вре-
мени выполнения. 
     В Ассемблерном модуле пишутся процедуры которые будут соответствовать
процедурам или функциям Паскаля. После чего Ассемблерные модули транслиру-
ются в .obj файлы и линкуются. Линкование выполняет сам компилятор Паскаля,
следовательно, правило интерфейса определяется языком, на котором написан
главный модуль (т.е. Паскалем). 

     [ Адресация. ]

     В TP используются far и near процедуры
     Ближняя адресация используются для:
     1) вызова процедур, расположенных в главном модуле.
     2) вызова вложенных процедур
     3) вызова процедур, описанных в  implementation  и  не  описанных в
interface.

     Дальняя адресация используются для:
     1) Процедур, описанных в секции Interface
     2) Для любых процедур с явно указанной директивой {$F+} (после этой
директивы все процедуры будут оформлены как дальние до директивы {$F-})
или для процедур, описанных с использованием директивы FAR.

     В пределах одного сегмента на Ассемблере можно использовать как
дальнюю так и ближнюю адресацию. При этом работа программы не изменится.
Однако при дальней адресации в стеке адрес возврата будет состоять из двух
слов: сегмент+смещение. Если используется несколько сегментов, то п/п,
вызываемые из других сегментов должны быть описаны как дальние.

     [ Передача параметров. ]

     Паскаль погружает параметры в стек в порядке их описания (в отличие от Си).
Выход из подпрограммы осуществляется командой ret data (т.е стек очищает
Ассемблерный модуль, опять таки, в отличие от Си), где data - кол-во байт,
переданных подпрограмме в качестве параметров.
     Ссылки (указатели) в Паскале всегда задаются 2 словами (сегмент:смещение).
По ссылке передаются все параметры переменные; массивы и записи, если их размер
превышает 4 байта. Множества и строки всегда передаются по ссылке.
     В стек погружаются:
    * Тип boolean как байт с фиксированным значением 0 или 1.
    * byte, char, shortint как байт.
    * Перечисляемый тип, как слово.
    * integer или word как 2 байта.
    * longint как 2 слова.
    * real как 3 слова.

     [ Возврат значений функциями. ]

     Ассемблерные функции должны следующим образом возвращать результат
своей работы:
    * длиной 1 байт (Byte, Char и т.п.) - в регистре AL;
    * длиной 2 байта (Integer, Word) - в регистре АХ;
    * длиной 4 байта (Pointer, Longlnt) - в регистрах DX (старшее слово или
сегм.) и в АХ (младшее слово или смещ.);
    * типа Real - в регистрах DX, BX, АХ (старшее слово - в DX, младшее в АХ);
    * вещественных типов Single, Double, Extended, Comp - в регистре ST(0) со-
процессора;
    * строкового типа - во временной области памяти, на которую ссылает-
ся один из параметров в стеке. Подробнее о строках мы поговорим в разделе
"Обработка строк".
     Интересное замечание: в книге В.В. Фаронова "ТР 7.0" написано, что
строки должны возвращаться по ссылке в DX:AX. Лично мне это не понятно:
куда я должен записать строку чтобы возвратить на нее ссылку? Возможно, это
опечатка... Но об этом чуть позже ;)

     [ Подключение asm модуля. ]

     Подключение asm модуля имеет ряд  особенностей:
     ASM модуль может содержать сегмент данных со стандартными именами :
CODE (CSEG) - сегмент с п/п, DATA(DSEG) - лок. переменными, CONST - 
типизированные констранты. Сегмент кода объединяется с основным сегментом
кода  паскалевской программы, аналогично включается сегмент данных. Сегмент
стека объявлять не требуется т.к. будет использоваться сегмент Паскаля.
     Переменные сегмента данных являются локальными для asm  модуля,  то
есть паскалевской программе они не известны.  Даже  если  определить  их
конкретные значения db, dw, то в результирующей программе они не опреде-
лены.
     В  паскалевской  программе  asm  процедуры  описываются  директивой
external. Транслируя asm модуль мы получаем результат - .obj - файл,
а в начале паскалевской программы мы записываем директиву подключения моду-
ля {$L .obj}.
     Для того чтобы скомпилировать модуль с созданием листинга и добавлением
отладочной информации нужно передать параметром td.exe имя исходного текста
модуля и указать некоторые директивы:

     td.exe /zi xxx.asm /la


     Где td.exe   - компилятор ЯА
         /zi      - параметр, указывающий на добавление отладочной информации
         xxx.asm  -
         /la      - параметр, указывающий на создание листинга

     Приведем пример:

Program lab5;
Uses crt;
Type      tmas = array[1..10] of integer
Var       a,b:tmas
          i,j:byte
          na,n,b:word
{$L obrab.obj} { тут мы подключили внешний модуль }
{$F+} {все процедуры после этой директивы будут иметь дальнюю адресацию}
Procedure M_ch_zr (Var x:tmas; n:word); external;
Function max_cl (Var x:tmas; n:word):integer; external;
{$F-}
{Процедуры ввода-вывода массивов}
Begin { main }
  in_massiv (a,na,'A')
  in_massiv (b,nb,'B');
  clrscr;
  out_massiv (a,na,'Исходный массив A');
  Writeln ('Максимальный элемент -',max_el(A,nA));
  M_ch_zr (A,na);
  Out_mas (A,na,'Преобразованный массив A')
End. { /main }



     А вот и сам модуль:


CODE      SEGMENT
PUBLIC    M_ch_Zr
ASSUME    CS:CODE
M_ch_zr   PROC      FAR            ;замена <0 на 0
          PUSH      CX DI BP ES
          mov       BP,SP

;    В данный момент в стек выглядит следующим образом:
;           │        │
;           ├────────┤
;           │ ES     │bp+0
;           ├────────┤
;           │ BP     │ +2
;           ├────────┤
;           │ DI     │ +4
;           ├────────┤
;           │ CX     │ +6
;           ├────────┤
;  адрес   ┌┤смещение│ +8
;  возврата│├────────┤
;          └┤сегмент │ +10
;           ├────────┤
;           │  n     │ +12
;           ├────────┤
;  адрес   ┌┤смещение│ +14
;  x       │├────────┤
;          └┤сегмент │ +16
;           └────────┘

          mov       CX,[BP+12]     ; n
          les       DI,[BP+14]     ; адрес X
c:        mov       AX,es:[DI]
; при трансляции перед этой командой ставится префикс изменения сег-
; мента адресации. При отсутствии ES:  адресация  автоматически  де-
; лается DS:[DI]. Префикс действует только на одну команду.
          cmp       AX,0
          jge       M
          mov       word PTR ES:[DI],0
M:        add       DI,2
          loop      C
          pop       ES BP DI CX
          ret       6
M_ch_zr   ENDP
CODE      ENDS
          END
; Как видите, этот модуль практически ничем не отличается от обычной
; программы на Ассемблере, но отличия все же есть: это, во-первых,
; строго заданные имена сегментов и, во-вторых, вконце модуля стоит END
; без параметров. Это нужно помнить!



     Ранее отмечено, что процедуры asm модуля попадают  в  сегмент  кода
паскалевской программы, поэтому использование команды les является  избы-
точным, т.к. в этом случае ES=DS.

Max_el    PROC      FAR
          push      CX DI BP ES
          mov       BP,SP
          mov       CX,[BP+12]
          mov       DI,[BP+14]
          mov       AX,DI
C1:       cmp       AX,[DI]
          jge       M1
          mov       AX,[DI]
M1:       add       [DI],2
          loop      C1
          pop       ES BP DI CX
          ret       6
Max_el    ENDP



     По завершению процедуры в AX - max_el, который и возвращается в ка-
честве результата функции в паскалевскую программу.

     start     stop      length    name      class
     00000h    00343h    00344h    lab5      code
       от        до      всего
     00350h    00962h    00613h    crt       code
     --------------------------    system    code
     01390h    ----------------    data      data
         глобальный сегмент данных pas программы
     01660h    0505fh    04000h    stack     stack
                          16 Kb
     05660h    05660h    00000h    heap      heap
     Динамические переменные, т.к. куча не используется.

     [ Использование глобальных переменненых. ]

     В подключаемых Asm модулях возможно использовать глобальные перемен-
ные из Паскаля. Для этого существует специальная директива EXTRN: 

EXTRN  <имя>:<тип>,<имя>:<тип>, ...
где для имен переменных используются типы:  byte, word ,dword(в зависимос-
ти от размера переменной),а для меток и имен процедур типы: far, near
(в зависимости от адресации).

Эта директива позволяет объявить в Asm модуле внешние(по отношению к моду-
лю) переменные. Она позволяет работать с глобальными переменными, объяв-
ленными в секции VAR. К сожалению, таким образом нельзя обращаться
к константам из паскаля т.к. они находятся в другом сегменте =(...
     Примечание: для того чтобы узнать что именно из Паскаля можно исполь-
зовать в Asm модуле нужно зайти в меню Options->Linker и отметить
пункт Map file := Public. После этого откомпилировать программу и
посмотреть файл с расширением .map. Рассмотрим пример:

Program Prime_p;
Const
  N=255;
Type
  SetOfNumb = set of 1..N;
Var
  n1,next,i : word;
  BeginSet,
  PrimeSet  : SetOfNumb;
begin
  { Тут что-то происходит... }
end.



 Для такого исходника получем такой .map файл:

 Start  Stop   Length Name               Class
(Это описание сегментов)
 00000H 00145H 00146H Prime_p            CODE
 00150H 00B50H 00A01H System             CODE
 00B60H 00E43H 002E4H DATA               DATA
 00E50H 04E4FH 04000H STACK              STACK
 04E50H 04E50H 00000H HEAP               HEAP

  Address         Publics by Value
(А это то, что нас интересует)
 0000:0020       @
 00B6:0002       OvrCodeList
 00B6:0004       OvrHeapSize

      . . . . . . .

 00B6:004C       Test8086
 00B6:004D       Test8087
 00B6:004E       FileMode
 00B6:0052       n1
 00B6:0054       next
 00B6:0056       i
 00B6:0058       BeginSet
 00B6:0078       PrimeSet
 00B6:0098       Input
 00B6:0198       Output
 00B6:0298       SaveInt00
 00B6:029C       SaveInt02

       . . . . . . .
 
 00B6:02D4       SaveInt3D
 00B6:02D8       SaveInt3E
 00B6:02DC       SaveInt3F
 00B6:02E0       SaveInt75

(А это точка входа в программу)
Program entry point at 0000:0020


     Как видно из этого файла, компилятор создает много переменных, доступ-
ных во внешних модулях. Из всех этих переменных наши - выделенны жирным.
Использование переменных компилятора возможно, но делать это крайне
не рекомендуется!

     Таким образом, мы можем использовать в модуле след. переменные:


CODE      SEGMENT
PUBLIC    xxx
EXTRN n1:word, next:word, i:word . . .
ASSUME    CS:CODE
       . . . . . . .     



     [ Особенность обработки матриц. ]

     Обработка матриц в asm модуле имеет свои "тонкости". Рассмотрим это
подробнее:
     Пусть, в Паскалевской программе матрица объявлена след. образом:


Program Matrix_asm;
Uses CRT;
Const
  max_n = 10;
  max_m = 5; 
Type
  TMatrix = array[1..max_n, 1..max_m]  of integer;
Var
  Matrix : TMatrix;
  m,n    : word;
Begin
  n:=0;
  m:=0;
  repeat
    ClrScr;
    write('Введите размеры матрицы n, m');
    readln(n,m)
  until (n>0 and n <= max_n) and (m>0 and m <= max_m);
  { Здесь осуществляется ввод матрицы }
            . . .
  { Здесь осуществляется обработка матрицы }
            . . .
  { Здесь осуществляется вывод матрицы }
End.     



     При таком объявлении матрицы наша переменная (Matrix) в памяти
будет занимать такой участок:

         1        n     max_n
         ╔══╤═ . ═╗ . ─┬──┐
         ╟──┼─ . ─╢ . ─┼──┤
           . . . . . . . .
       m ╚══╧═ . ═╝ . ─┼──┤
           . . . . . . . .
         ├──┼─ . ─┼ . ─┼──┤
    max_m└──┴─ . ─┴ . ─┴──┘

     На данном рисунке двойной линией обозначено реально используемое
пространство матрицы, а одинарной - полный объем памяти, занимаемый 
матрицей.
     Таким образом, чтобы обратиться ко второму эл-ту третей строки
нужно использовать смещение: "((3-1) * max_n + (2-1)) * 2", а не 
"3 * n + 2", как может показаться на первый взгляд. В конце нужно
умножить на размер элемента в байтах (в нашем случае это 2 байта - раз-
мер типа integer). Это следует учитывать при обработке матриц. Для того
чтобы корректно обработать матрицу в процедуру обработки нужно передавать
не только адрес матрицы, n и m но и длину строки (в нашем случае это max_n).

{ Т.е. процедура должна быть описана так: }
  Procedure Do_Something(Var Matr: TMatrix; n,m,max_n: word); external;

{ А вызов, соответственно, делать так: }
  Do_Something(Matrix, n,m,max_n);



     [ Обработка строк. ]

     Обычно функция возвращает одно скалярное значение. Но в TP есть исключе-
ние. Это тип string, который является структурированным (массивом символов),
однако с другой стороны имеются средства обработки как скаляра (операция
присвоения, операции сравнения и др.).

     Рассмотрим следующий пример из состава TASM:

; Turbo Assembler example. Copyright (c) 1993 By Borland International, Inc.
;
; HEX.ASM
;
; Usage: Run tasm on this file and link with hex.pas

CODE      SEGMENT
          ASSUME cs:CODE,ds:NOTHING

; Parameters (+2 because of push bp)

byteCount EQU BYTE PTR  ss:[bp+6]  ; а это параметры
num       EQU DWORD PTR ss:[bp+8]       ; функции HexStr.

; Function result address (+2 because of push bp)

resultPtr EQU DWORD PTR ss:[bp+12]      ; это результат функции

HexStr    PROC FAR
          PUBLIC HexStr

          push bp
          mov bp,sp          ;get pointer into stack
          les di,resultPtr   ;get address of function result
          mov dx,ds          ;save Turbo's DS in DX
          lds si,num         ;get number address
          mov al,byteCount   ;how many bytes?
          xor ah,ah          ;make a word
          mov cx,ax          ;keep track of bytes in CX
          add si,ax          ;start from MS byte of number
          dec si
          shl ax,1           ;how many digits? (2/byte)
          cld                ;store # digits (going forward)

; Важно!!! Не забывайте записывать длину строки в нулевой байт!
          stosb              ;in destination string's length byte

HexLoop:
          std                ;scan number from MSB to LSB
          lodsb              ;get next byte
          mov ah,al          ;save it
          shr al,1           ;extract high nibble
          shr al,1
          shr al,1
          shr al,1
          add al,90h         ;special hex conversion sequence
          daa                ;using ADDs and DAA's
          adc al,40h
          daa                ;nibble now converted to ASCII
          cld                ;store ASCII going up
          stosb
          mov al,ah          ;repeat conversion for low nibble
          and al,0Fh
          add al,90h
          daa
          adc al,40h
          daa
          stosb
          loop HexLoop       ;keep going until done
          mov ds,dx          ;restore Turbo's DS
          pop bp
          ret 6              ;parameters take 6 bytes
HexStr    ENDP
CODE      ENDS
          END



 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


{ Turbo Assembler example. Copyright (c) 1993 By Borland International, Inc. }

{ Use with hex.asm }

Program HexTest;
Var
  num : Word;

Function HexStr (Var num; byteCount : Byte) : string; far; external;

{$L HEX.OBJ}

Begin
  num := $face;
  Writeln('The Converted Hex String is"',HexStr(num,sizeof(num)),'"');
End.



     Не вникаясь в алгоритм функции рассмотрим как выглядит стек при
вызове HexStr(num,sizeof(num)).

              ВР+0              │ старый ВР    │
                                ├──────────────┤
              ВР+2,4            ├ адр. возвр.  ┤  (4 байта)
                                ├──────────────┤
              ВР+6              │ sizeof(num)  │ ─┐
                                ├──────────────┤  │  Все параметры
              ВР+8        адрес┌┤  смещение    │  ├  данной функции
                           num │├──────────────┤  │  занимают 6 байт
              ВР+10            └┤  сегмент     │ ─┘
                                ├──────────────┤
              ВР+12       адрес┌┤  смещение    │  Это поле в стек
                          врем.│├──────────────┤  поместил сам компилятор.
              ВР+14       поля └┤  сегмент     │  При расчете параметра ret
                                └──────────────┘  это поле не учитывать!
                                                  Именно поэтому в примере
                                                  стоит ret 6, а не ret 8.

     Итак, что же это все значит? Ну, по порядку:
     В стеке лежит адрес возврата и адрес обрабатываемого поля num. Тут
все понятно... Но вот что за адрес временного поля? На самом деле, компиля-
тор помещает в стек адрес поля памяти, куда следует записать результат.
После работы подпрограммы компилятор сам позаботится о копировании
этого поля в результирующую строку.

     [ Макростредства в помощь ]

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

; Turbo Assembler example. Copyright (c) 1993 By Borland International, Inc.
;
; ENVSTR.ASM - Example program to scan the DOS environment
;
; Usage: Run tasm on this file and link with envstr.pas

          .MODEL  large,PASCAL
; Эта директива создает сегменты по умолчанию и соответствующие им
; выражения ASSUME и GROUP. В кач-ве параметра указана модель памяти,
; полностью соответствующая Паскалю.
          .DATA
; А тут мы начали сегмент данных.
          EXTRN prefixSeg : WORD  ;gives location of PSP
; А тут одной строкой объявили адрес PSP.
          .CODE
; А вот и сегмент кода.
EnvString PROC FAR  EnvVar:DWORD  RETURNS EnvVal:DWORD
; Тут мы объявили дальнюю функцию с параметром EnvVar размером в 4 байта
; (сегм. + смещ.) и указали что EnvVal тогоже размера будет
; нашой переменной, куда мы запишем результат ;)
          PUBLIC  EnvString
; А это для того чтобы Паскаль смог "увидеть" нашу функцию.
; Это что-то типа объявления функций в интерфейсе модуля ;)
; И еще, как видите, в начале подпрограммы команды "push bp" и
; "mov bp,sp" писать не нужно. За вас это сделает компилятор ;)
          cld                     ;work upward
          mov     es,[prefixSeg]  ;look at PSP
          mov     es,es:[2Ch]     ;ES:DI points at environment
          xor     di,di           ;which is paragraph-aligned
          mov     bp,sp           ;find the parameter address
          lds     si,EnvVar       ;which is right above the return address
          ASSUME  ds:NOTHING
          lodsb              ;look at length
          or      al,al      ;is it zero?
          jz      RetNul     ;if so, return
          mov     ah,al      ;otherwise, save in AH
          mov     dx,si      ;DS:DX contains pointer to first parm character
          xor     al,al      ;make a zero
Compare:
          mov     ch,al      ;we want ch=0 for next count, if any
          mov     si,dx      ;get back pointer to string sought
          mov     cl,ah      ;get length
          mov     si,dx      ;get pointer to string sought
          repe    cmpsb      ;compare bytes
          jne     Skip       ;if compare fails, try next string
          cmp     byte PTR es:[di],'='
                             ;compare succeeded; is next char '='
          jne     NoEqual    ;if not, still no match
Found:
          mov     ax,es      ;make DS:SI point to string we found
          mov     ds,ax
          mov     si,di
          inc     si         ;get past the equal (=) sign
          les     bx,EnvVal  ;get address of function result
          mov     di,bx      ;put it in ES:DI
          inc     di         ;get past the length byte
          mov     cl,255     ;set up a maximum length
CopyLoop:
          lodsb              ;get a byte
          or      al,al      ;zero test
          jz      Done       ;if zero, we're done
          stosb              ;put it in the result
          loop    CopyLoop   ;move up to 255 bytes
Done:     not     cl         ;we've been decrementing CL from
                             ; 255 during save
          mov     es:[bx],cl           ;save the length
          mov     ax,@DATA
; А тут мы в АХ поместили сегмент данных ;)
          mov     ds,ax                ;restore DS
          ASSUME  ds:@DATA
          ret
; Как видите, тут ret без параметров ;)
; А знаете почему? А потому что компилятор сам сюда напишет все что
; нужно: восстановит ВР и напишет retf с нужным параметром в зависимости
; от входных параметров. В нашем случае выход будет таким:
; "pop bp" + "retf 4".
          ASSUME  ds:NOTHING
Skip:
          dec     di         ;check for null from this char on
NoEqual:
          mov     cx,7FFFh   ;search a long way if necessary
          sub     cx,di      ;environment never >32K
          jbe     RetNul     ;if we're past end, leave
          repne   scasb      ;look for the next null
          jcxz    RetNul               ;exit if not found
          cmp     byte PTR es:[di],al  ;second null in a row?
          jne     Compare              ;if not, try again
RetNul:
          les     di,EnvVal            ;get address of result
          stosb                        ;store a zero there
          mov     ax,@DATA
          mov     ds,ax                ;restore DS
          ASSUME  ds:@DATA
          ret
; Как видите, тут тоже ret без параметров(см. выше) ;)
EnvString ENDP
          END
; И еще, как вы заметили, ребята из Борланда не сильно беспокоятся о регистрах.
; Точнее беспокоятся только о DS, SS и SP, а о ВР заботится сам компилятор при
; входе и выходе из п/п (только при использовании ключевого слова PASCAL в
; объявлении п/п или при указании модели памяти!). Т.е. Все остальные регистры
; к моменту выхода из п/п могут иметь произвольные значения ;-). Исключение
; составляют только функции, которые возвращают результат в некоторых из
; регистров ЦП.



 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


{ Turbo Assembler example. Copyright (c) 1993 By Borland International, Inc. }

{ Use with envstr.asm }

Program EnvTest;
{ program looks for environment strings }

Var
  EnvVariable : string;
  EnvValue : string;

Function EnvString(s:string) : string; far; external;
{$L ENVSTR.OBJ}
Begin
  EnvVariable := 'PROMPT';
  EnvValue := EnvString(EnvVariable);
  if EnvValue='' then EnvValue := '*** not found ***';
  Writeln('Environment Variable:',EnvVariable,'Value:',EnvValue);
End.



     [ Резюме ]

     Ну что ж, вспомним все, о чем говорилось в этой статье...
     Asm модули, восновном, используются для увеличения производительности
программы. 
     В одном модуле можно использовать как ближнюю, так и дальнюю адресацию,
но если в модуле используются межсегментные вызовы, то такие п/п должны иметь
дальнюю адресацию. Кстати, при дальней адресации значение CS при выходе из п/п
может быть произвольным. Э то связано с тем, что при такой адресации адрес
возврата состоит из 4 байт (сегм.+смещ.).
     Параметры передаются в стек в порядке их описания. Адреса всегда переда-
ются дальними. По ссылке передаются все параметры переменные; массивы и записи,
если их размер превышает 4 байта. Множества и строки всегда передаются по ссылке.
Стек чистит сама п/п использую ret data.
     Функции возвращают свои значения в регистрах (см. "Возврат значений функ-
циями"). Исключение чение составляют строки - их сохраняют во времееной облас-
ти памяти.
     Asm модули практически не отличаются от обычных программ на ЯА. Модули
должны иметь строго определенные имена сегментов (CODE, DATA, STACK). Эти
сегменты будут объеденены с Паскалевскими. В конце модуля стоит END без пара-
метров. Возможны следующие варианты оформления:

     Пусть дана такая процедура:

     procedure proc_name(Var a: byte; b: integer; c: longint);


     Тогода:


CODE      SEGMENT
PUBLIC    proc_name
ASSUME    CS:CODE
proc_name PROC      FAR
          PUBLIC  proc_name
          push bp ds ss sp
          mov  bp,sp
a         equ  [bp+12]    ;   Это
b         equ  [bp+10]    ;     параметры
c         equ  [bp+6]     ;       процедуры
 . . .
          pop  sp ss ds bp
          ret  10     ; 10 - кол-во переданных байт
proc_name ENDP
CODE      ENDS
          END

;     Процедуру можно было объявить и так:

proc_name PROC FAR PASCAL, a:dword,b:word,c:dword ; Это параметры
          PUBLIC  proc_name
          push ds ss sp
 . . .
          pop  sp ss ds
          ret ; без параметров
proc_name endp

;          или

          .MODEL  large,PASCAL
          .DATA
 . . .          
          .CODE
proc_name PROC FAR  a:dword,b:word,c:dword ; Это параметры процедуры
          PUBLIC  proc_name
          push ds ss sp
 . . .
          pop  sp ss ds
          ret ; без параметров
proc_name ENDP
          END



     [ Приложение А. Работа с множествами. ]

     Многих давно итересует как же на самом деле работают множества и какова
их внутренняя структура... 
     Итак, максимальный кол-во эл-тов в мн-ве 256 (т.е от 0 до 255) хотя,
мн-во может быть пустым ([]). Каждому элементу мн-ва соответствует 1 бит
(1 - элемент присутствует в мн-ве, 0 - отсутствует). Таким образом мак-
симальный объем памяти, занимаемый множеством не может превышать 32 байта
(256 бит, по биту на элемент). Однако, как вы знаете, минимальный объем
памяти равен 1 байту т.е. даже если вы обявите мн-во как "X: set of 1..1" то
его размер все равно будет равен 1 байту и более того, вы даже можете
сделать такое:

     b:=5;
     Include(X,b);



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

     Пусть заданы мн-ва "X,Y: set of 0..7;"
     Тогда операция "X:=[];" на самом деле обнуляет участок памяти с мно-
жеством т.е. это будет "0000 0000".
     Операция "Include(X,3);" будет выглядеть так:

    old X  0000 0000
        or
       [3] 0001 0000
        =
    new X  0001 0000

     Y:=X+[2..4,7]



        X  0001 0000
        or
  [2..4,7] 0011 1001
        =
        Y  0011 1001

     X:=X*Y



    old X  0001 0000
       and
        Y  0011 1001
        =
    new X  0001 0000

     X:=X-[2..3];



     А тут немного сложнее: для начала инвертируем [2..3], а потом
умножаем:

         [2..3]  0011 0000
     not [2..3]  1100 1111

          old X  0001 0000
              *
     not [2..3]  1100 1111
              =
          new X  0000 0000

     И в заключение приведу пример работы с множествами в asm модуле:

; File name: "Prime_m.asm"
; Работа с множествами (решето Эратосфена).
; Данный файл является asm модулем к программе "Prime_a.pas"
        .MODEL  large,PASCAL
        .DATA
n1       dw      ?
next     dw      2
BytesInSet dw    ?
        .CODE
PUBLIC CalcPrime
CalcPrime proc far
        push    bp
        mov     bp,sp
Set_Adr      equ [bp+8]
N    equ [bp+6]
        mov     si,Set_Adr   ; si - адрес исходного мн-ва

        mov     ax,N
        mov     dl,8
        div     dl
        sub     dl,ah
        add     N,dl
        dec     byte ptr N
        mov     dx,N         ; dx=N

        mov     cx,dx
        shr     cx,3
        inc     cx
        mov     BytesInSet,cx   ; BytesInSet=SizeOf(Set);

        mov     di,si
c:       mov     byte ptr [di],0 ; Set:=[];
        inc     di
        loop    c
        mov     byte ptr [si],2 ; Set:=[1]

        sub     sp,BytesInSet   ; Создадим вспомогательное мн-во в стеке
        mov     di,sp           ; di - адрес нового мн-ва
        push    di
        mov     cx,BytesInSet
c1:      mov     byte ptr [di],0FFh ; Set:=[0..N];
        inc     di
        loop    c1
        pop     di
        mov     byte ptr [di],11111100b    ; Set:=[2..N];
ccc:                      ; While Set <> []
        mov     al,0
        cld
        mov     cx,BytesInSet
        push    di si
        repe    scasb
        pop     si di
        je      m_end
        push    next
        pop     n1
m_wh:                     ; while n1 do begin<=N
        cmp     n1,dx
        ja      m_endd
        mov     ax,n1
        call    Excl
        mov     ax,next
        add     n1,ax
        jmp     m_wh
m_endd:                   ; end { while n1<=N }
        mov     ax,next
        call    Incl

m_rep:                    ; repeat
        inc     next
        mov     ax,next
        Call    In_s
        jnz     m_end_rep
        cmp     next,dx
        ja      m_end_rep
        jmp     m_rep

m_end_rep:                ; until (next in Set) or (next > N)

        jmp     ccc

m_end:
        add     sp,BytesInSet   ; Удалим вспомогательное мн-во в стеке
        pop     bp
        ret     6
CalcPrime endp
; - - - - - - - - - -
Incl    proc near
; Set:=set+[al]
        push    si bx cx
        xor     ah,ah
        mov     bx,ax
        shr     bx,3
        add     si,bx
        shl     bx,3
        sub     ax,bx
        mov     bx,1
        mov     cl,al
        shl     bx,cl
        or      [si],bx
        pop     cx bx si
        ret
Incl    endp
; - - - - - - - - - -
Excl    proc near
; Set:=set-[al]
        push    di bx cx
        xor     ah,ah
        mov     bx,ax
        shr     bx,3
        add     di,bx
        shl     bx,3
        sub     ax,bx
        mov     bx,1
        mov     cl,al
        shl     bx,cl
        not     bx
        and     [di],bx
        pop     cx bx di
        ret
Excl    endp
; - - - - - - - - - - - - -
In_s    proc near
; [al] in Set ?
        push    di bx cx
        xor     ah,ah
        mov     bx,ax
        shr     bx,3
        add     di,bx
        shl     bx,3
        sub     ax,bx
        mov     bx,1
        mov     cl,al
        shl     bx,cl
        and     bx,[di]
        pop     cx bx di
        ret
In_s    endp
        end



{
 File name: "Prime_a.pas"
 Работа с множествами (решето Эратосфена).
 Данный файл компилируется вместе с "Prime_m.obj"
}
Program Prime_a;
Const
  N=255;
Type
  SetOfNumb = set of 1..N;
Var
  i : word;
  PrimeSet   : SetOfNumb;
{$L prime_m.obj}
Procedure CalcPrime(Var SetOfNumb; n:word); far; external;
Begin
  CalcPrime(PrimeSet,N);
  for i:=1 to N do
    if i in PrimeSet then
      Write(i:8);
  WriteLn;
End.



     [ THE END ]

				(С) Доц. каф. ЭВМ Теплинский С.В.,
				    ст. гр. КС-03а Лабинский Николай.
				ДонНТУ 2004-2005.

Last one modified at: GMT+2 23:41:05 13.09.2005

Рейтинг@Mail.ru