
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.97.14.85] |
![]() |
|
Сообщ.
#1
,
|
|
|
Внешние процедуры (функции) С помощью внешних процедур (функций) можно осуществить вызов из программы процедур или функций, написанных на языке ассемблера. Ассемблер обеспечивает компиляцию программ, написанных на машинно-ориентированном языке программирования низкого уровня. В Турбо Паскале есть собственный встроенный ассемблер см. гл.12). В этом разделе речь идет о программах, написанных и откомпилированных с помощью внешнего ассемблера, такого как, например, ассемблер фирмы MicroSoft или Turbo Assembler фирмы Borland. Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности использования всех особенностей архитектуры ПК. Ассемблерные программы выполняются значительно быстрее и занимают меньший объем памяти, чем программы, написанные на Турбо Паскале, однако низкий уровень языка ассемблера существенно снижает производительность труда программиста и резко усложняет отладку программ. Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, в которых используются недоступные из Турбо Паскаля особенности архитектуры ПК. Внешняя процедура (функция) в программе, написанной на Турбо Паскале, объявляется своим заголовком, за которым следует стандартная директива EXTERNAL, например: ![]() ![]() Function LoCase (ch : char):char; external; ![]() ![]() Procedure Swapping (var a,b; N:word); external; Как видно из этих примеров, тело внешней процедуры (функции) отсутствует - его заменяет директива EXTERNAL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать и получить объектный файл с расширением OBJ, содержащий перемещаемый код ассемблерной программы. Непосредственно перед описанием внешней процедуры (функции) в тело основной программы вставляется директива компилятора {$L<имя файла>}, где <имя файла> - имя OBJ-файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указываются опцией OPTIONS/DIRECTORIES/OBJECT DIRECTORIES (см. прил. 1). Перед передачей управления внешней процедуре (функции) программа помещает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры ВР, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в программу. Остальные регистры можно не сохранять и соответственно не восстанавливать. Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек помещается указатель, содержащий абсолютный адрес параметра, если по значению - в стек помещается сам параметр, точнее - его значение. Все параметры-переменные, т.е. параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры-значения могут передаваться по ссылке или по значению в зависимости от длины внутреннего представления соответствующего параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, он передается своим значением, т.е. его значение помещается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, начиная с версии 5.0 -через стек центрального процессора 8086/80486). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий параметр передается по ссылке. Ассемблерные функции в зависимости от длины внутреннего представления результата должны возвращать его через регистры центрального процессора или сопроцессора по следующим правилам: длиной в 1 байт - в регистре AL; длиной в 2 байта - в регистре АХ; длиной в 4 байта - в регистрах DX:AX (старшее слово в DX); тип REAL (6 байт) - в регистрах DX:BX:AX; типы SINGLE, DOUBLE, EXTENDED и СОМР - через стек сопроцессора 8087/80486; указатели - в регистрах DX:AX (сегмент в DX); строки возвращаются по ссылке: адрес начала строки помещается в DX:AX (сегмент в DX). Все ассемблерные процедуры должны размещаться в сегменте с именем CODE или CSEG, или с именем, оканчивающимся на _ТЕХТ; инициализированные локальные Переменные помещаются в сегмент с именем CONST или с именем, оканчивающимся на _DATA. Все другие локальные переменные необходимо размещать в сегменте с именем DATA или DSEG, или с именем, оканчивающимся на _BSS. Любые другие объявления сегментов игнорируются. Все имена, объявленные в интерфейсной части модулей программы, написанной на Турбо Паскале, становятся доступны ассемблерной процедуре (функции) после их объявления директивой EXTRN. Точно так же все имена ассемблерных процедур и функций, которые должны быть доступны программе на Турбо Паскале, следует объявлять директивой PUBLIC. Ozzя © |
![]() |
Сообщ.
#2
,
|
|
< Связь Паскаля с Ассемблером >
Посвящается Тиkи. Связь Паскаля с Ассемблером реализуется как многомодульная программа, где главный модуль написан на Паскале, а вспомогательный на языке Ассемблера. Обычно, в Ассемблерном модуле реализуется часть алгоритма программы, которую выполнить в Паскале трудно или данная часть критична к времени выполнения. В Ассемблерном модуле пишутся процедуры которые будут соответствовать процедурам или функциям Паскаля. После чего Ассемблерные модули транслируются в .obj файлы и линкуются. Линкование выполняет сам компилятор Паскаля, следовательно, правило интерфейса определяется языком, на котором написан главный модуль (т.е. Паскалем). [ Адресация. ] В TP используются far и near процедуры Ближняя адресация используются для: Дальняя адресация используются для: для любых процедур с явно указанной директивой. В пределах одного сегмента на Ассемблере можно использовать как дальнюю так и ближнюю адресацию. При этом работа программы не изменится, однако в стеке адрес возврата будет состоять из двух слов: сегмент+смещение. Если используется несколько сегментов, то процедуры должны быть описаны как дальние. [ Передача параметров. ] Параметры погружаются в стек в порядке их описания (в отличие от Си). Выход из подпрограммы осуществляется командой ret data (т.е стек очищает Ассемблерный модуль, опять таки, в отличие от Си), где data - кол-во байт, переданных подпрограмме в качестве параметров. Ссылки (указатели) в Паскале всегда задаются 2 словами (сегмент:смещение). По ссылке передаются все параметры переменные; массивы и записи, если их размер превышает 4 байта. Множества и строки всегда передаются по ссылке. В стек погружаются: [ Возврат значений функциями. ] Ассемблерные функции должны следующим образом возвращать результат своей работы: * длиной 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 - файл. В начале паска- левской программы записывается директива подключения .obj - модуля: ![]() ![]() Program lab5; Uses crt; Type tmas = array[1..10] of integer; Var a,b: tmas; i,j: byte; na,n: 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 модуле имеет свои "тонкости". Рассмотрим это подробнее: Пусть, в Паскалевской программе матрица объявлена след. образом: ![]() ![]() 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,n); и это будет работать... ![]() Из всего вышеперечисленного следует что вся работа над множествами состоит из логических операций над памятью. Пример: Пусть заданы мн-ва "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 ] Ну что, вроде все что я хотел сказать... Всем удачи ![]() (С) Лабинский Николай aka e-moe. ДонНТУ 2004-2005. I love Borland. < Написано по мотивам конспекта лекций по "СП" Теплинского С.В, а также, книги Фаронова В.В. "Turbo Pascal 7.0" и, естественно, книги П.Абеля "ЯА" > При написании этой стати использовались: * Пиратские копии bp.exe, tasm.exe и td.exe. ![]() * И зарегистрированный FAR. %) Last one modified at: GMT+2 23:31:50 9.01.2005 Это сообщение было перенесено сюда или объединено из темы "Linking Asm to Pascal" |