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

Введение

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

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

Адресация

В TP используются far и near процедуры. Ближняя (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: 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.

ċ
Nikolay Labinskiy,
3 июн. 2010 г., 05:37