< Связь Паскаля с Ассемблером >

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

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

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

В TP используются far и near процедуры. Ближняя (near) адресация используются для:

  1. Вызова процедур, расположенных в главном модуле.
  2. Вызова вложенных процедур
  3. Вызова процедур, описанных в implementation и не описанных в interface.

Дальняя адресация используются для:

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

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

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

Паскаль погружает параметры в стек в порядке их описания (в отличие от Си). Выход из подпрограммы осуществляется командой ret data (т.е стек очищает Ассемблерный модуль, опять таки, в отличие от Си), где data - кол-во байт, переданных подпрограмме в качестве параметров.

Ссылки (указатели) в Паскале всегда задаются 2 словами (сегмент:смещение). По ссылке передаются все параметры переменные; массивы и записи, если их размер превышает 4 байта. Множества и строки всегда передаются по ссылке.

В стек погружаются:

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

Ассемблерные функции должны следующим образом возвращать результат своей работы:

Интересное замечание: в книге В.В. Фаронова "ТР 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: longint; 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 21:14:09 Mar 10, 2008