; Файл HEAP.mac comment~ Данный файл содержит описания макросов для работы со списковыми структурами данных (выделение из кучи и возврат в неё памяти, переход к предыдущему и последующему элементам и др.). Списки могут быть кольцевыми или линейными, одно- или двунаправленными, состоящими из элементов фиксированного или переменного размера. Перед началом работы создаётся ``головной элемент'', содержащий информацию о размере области, отводимой под кучу, указатели на некоторые (обычно первые) элементы списков и др. К головному элементу может добавляться дополнительная область памяти содержащая какую-либо специфи- ческую информацию, которую неудобно размещать в структуре. Уборка мусора в куче и дефрагментация свободного пространства в ней не производится. Элементы списков помимо прочего могут содержать указатели на динамически выделяемые блоки памя- ти и подсписки, подобно головной структуре. Вид головного элемента и кучи после выполнения процедуры настройки представлен на рис. 1. LBMN ─────────────╔══════════════════════════════╗ <─────────────────────────── Младшие ^ ║ ║ ^ ^ адреса │ ║ Заголовок головной структуры ║ │ │ │ ┌──║ ║ │ │ │ │ ╟──────────────────────────────╢ ───\ │Раз- │ "SizMem" │ │ ║ ║ ├<──"FNill"│мер │ │ │ ║ Структура пользователя SN ║ ───/ │глав- │ "LSizMem" │ │ ║ ║ │ного │ │ │ ╟──────────────────────────────╢ ─── │Эл-та │ │ │ ║ ║ ^ │"LSizM" │ │ │ ║ Неструктурированная память ║ │ SizExt │ │ v │ ║ ║ v v │ ──────────│ ╠══════════════════════════════╣ ──────────────── │ ^ │ ║ Область памяти, которая ║ ^ │ │ │ ║ может быть возвращена в ║ │ SFree │ │Куча │ ║ кучу ║ v │ │ └─>╠══════════════════════════════╣ <───────── │ │ ║ Свободный элемент и его ║ │ v ║ заголовок ║ v Старшие ──────────── ╚══════════════════════════════╝ ────────────────────────── адреса Рис. 1. Структура памяти в куче. Неструктурированная память может быть использована в тех случах, когда по тем или иным причи- нам размещение данных в основной структуре головного элемента SN нецелесообразно. Sfree --- часть памяти, которая может быть возвращена в кучу, но первоначально не включается в список свободной памяти. В процессе настройки может понадобиться установить некоторые динамические структуры, которые затем в процессе работы могут быть возвращены в список свободной памяти. Чтобы не расходовать лишнее время на вызовы процедур выделения памяти, необходимый объём может быть просто не включён в список свободных элементов и затем использован для размещения в нём динамических структур. LBMN --- метка в сегменте данных или косвенная ссылка через регистр на начало кучи (первый байт заголовка). Структура свободного элемента представлена на рис. 2. Список свободных элементов линейный одно- направленный из элементов переменного размера. Элементы свободной памяти не должны быть смежными. Если при возврате в кучу два или три элемента оказываются смежными, то они объединяются в один эле- мент. Память размером меньше размера заголовка свободного элемента не может быть возвращена в кучу, т. к. возможна порча данных других списков, поэтому выделяется не меньше размера заголовка (8 байт). Для единообразия программирования процедур выделения памяти из кучи и возврата в кучу в голов- ном элементе за указателем на первый свободный элемент следует поле размером в двойное слово LZero с нулевым значением (см. рис. 3). ──>│───────│<───заголовок │ │ ┌───┬───┬───────────────────┐ X -- указатель на следующий свободный элемент или nill │ Xi│ Yi│ свободный элемент│ Y -- размер свободного элемента └───┴───┴───────────────────┘ i -- порядковый номер элемента в списке │ │ │<─────────────────────────>│ Yi ^ │Xi-1 Рис. 2. ┌────────────┬──────┐ │ LSizM │4байта│ Размер главного элемента ├────────────┼──────┤ │ LSizMem │4байта│ Общий размер выделенной памяти ├────────────┼──────┤ │ LFirstFree │4байта│ Указатель на первый элемент списка │ │ │ свободной памяти ├────────────┼──────┤ │ LZero │4байта│ └────────────┴──────┘ Рис. 3. Элемент любого списка (за исключением списка свободной памяти) в общем случае выглядит так [reg-L?&SN&?Hd] ──>┌─────────────────────────────┐ │ Заголовок (L1Dir или L2Dir) │ Обязателен [reg-L?&SN&?ExtHd] ──>├─────────────────────────────┤ │ Пользовательская часть │ Необязателен │ заговка │ [reg] ──>├─────────────────────────────┤ │ структура данных (SN) │ Обязателен, но может быть пустым ├─────────────────────────────┤ │ Дополнительный блок памяти │ Необязателен. Если его нет, то поля │ блок памяти │ LSiz1 или LSiz2 не включаются в заголовок └─────────────────────────────┘ Здесь SN -- некоторая структура данных, её имя используется как имя типа; reg -- один из РОНов. "Размер данного элемента списка" включает размер заголовка, размер структуры элемента и размер памя- ти, выделенной после структуры. Кроме списков могут также выделяться блоки динамической памяти. Их вид следующий с указанием размера без указания размера [reg-L?&BlockName&?Hd]──>┌────────────┐ [reg-L?&SN&?ExtHd],──>┌────────────┐ │ Размер │ [reg-L?&BlockName&?Hd] │Польз. часть│ Необязателен [reg-L?&SN&?ExtHd]──>├────────────┤ │заголовка │ │Польз. часть│Необязателен [reg]──>├────────────┤ │заголовка │ │ данные │ [reg]──>├────────────┤ └────────────┘ │ данные │ └────────────┘ Размер блока с учётом заголовка (если есть) должен быть не меньше 8 байт! Иначе при его возвра- те в кучу может быть нарушена структура списков. Блоки могут быть типизированными и нетипизированны- ми. Для типизированных блоков фиксированного размера, размер увеличивается до 8 байт автоматически, для нетипизированных блоков и блоков переменного размера, он должен корректироваться программистом, например, с помощью макроса LCorrectSize. Типизированные блоки, в отличие от нетипизированных, опре- деляются с помощью макроса LDefBlockType, и к типизированным блокам может быть применён макрос LDefSubType, а к нетипизированным -- нет. На основе элментов списков и блоков выделенной памяти могут организовываться древовидные структуры и многомерные списки. Возможные варианты размещения ссылок на подсписки в элементе приве- дены ниже. Примерные виды списков: Линейный список Иерархический список с Многомерный список ┌─┐ ┌─┐ ┌─┐ кольцевым подсписком │P│──>│P│──>│P│───> ┌─┐ ┌─────────────┐ ^ ^ ^ ├─┤ ├─┤ ├─┤ ──│P│ └─>┌─┐ ┌─┐ │ ┌─┐ │ ┌─┐ │ ┌─┐ │ │D│ │D│ │D│ ^ ├─┤ ┌─>│P│──>│P│──┘ ──│P│─┘┌─> │P│─┘┌─>│P│─┘┌─> │D│ │D│ │D│ │ │D│ │ ├─┤ ├─┤ ^ ├─┤ │ ├─┤ │ ├─┤ │ │D│ │D│ │D│ │ │D│ │ │D│ │D│ │ │D│ │ │D│ │ │D│ │ └─┘ └─┘ └─┘ │ │D│ │ │D│ │D│ │ │D│ │ │D│ │ │D│ │ v ├─┤ │ └─┘ └─┘ v ├─┤ │ ├─┤ │ ├─┤ │ Тип А ──│P│──┘ ──│P│──┘ │P│──┘ │P│──┘ ├─┤ ├─┤ ├─┤ ├─┤ P --- указатель; │D│ Тип Б │D│ │D│ │D│ Тип В D --- к-л данные. └─┘ └─┘ └─┘ └─┘ Размещение ссылок на подсписки в таблице ссылок Поле является единственной Одно направление Ссылки двунаправленные ссылкой, а не таблицей для типов Б или В только для типа В Одно направление для типов Б и В ┌───────────┐ ┌───────────┐ ┌────────────┐ │ Заголовок │ │ Заголовок │ │ Заголовок │ [reg]───>├═══════════┤ [reg]───>├═══════════┤ [reg]───>├════════════┤ │ │ │ │ │ │ +Fld ├────────── │ +Fld ├────────── │ +Fld ├────────── │ ────────>├────P │ ─────────>├────PN │ ─────────>├────P │ ├────P │ ├────PP │ │ │ +[IReg*4] ├────P │ +[IReg*8] ├────PN │ └────────────┘ ────────>├────P │ ─────────>├────PP │ ├────P │ ├────PN │ Два направления только для типа В │ │ │ │ ┌────────────┐ └───────────┘ └───────────┘ │ Заголовок │ [reg]───>├════════════┤ ┌───────────┐ │ │ │Заголовок │ +Fld ├────────── │ [reg]───>├══════════─┤ ─────────>├────PN │ ├───────────┤ [reg]+Fld-1 ├────PP │ │LapTabl │ <──────────── └────────────┘ +Fld ├───────── │ ────────>├───P │ ├───P │ └───────────┘ Здесь P-- указатель на элемент подсписка; PN-- Указатель на следующий элемент подсписка, PP-- указа- тель на предыдущий элемент списка; Fld -- поле, являющееся ссылкой или массивом ссылок; IReg-- один из РОНов, содержащий номер интересующей ссылки в массиве ссылок, на начало которого указывает Fld. Размер одной ссылки 4 байта. Если используется двунаправленный список, то все ссылки сдвоенные (т. е. размер одного вхождения -- 8 байт), указывают на следующий и на предыдущий элементы. Перед табли- цей ссылок может размещаться необязательное однобайтовое поле, содержащее количество ссылок в табли- це (при этом, количество ссылок (одно- или двунаправленных) должно лежать в пределах от 1 до 256 иначе они не поместятся в поле). Поле LapTabl должно быть описано в структуре SN в байте, предшес- твующем началу таблицы ссылок. Использовать поле имеет смысл только при динамическом определении ко- личества ссылок. При статическом определении можно использовать более 256 ссылок. Проверка, выходит ли индекс за размер таблицы осуществляется макросом LChckInd, который должен указываться перед макро- сами, обращающимися к таблицам ссылок. Пример: LDefType macro SN1, 1 LDefSubType SN1, Fld1, 2, No, ItsTable, variable . . . LChckInd SN1, Fld1, ebx, ExErr_OutOfRange, edx, dl ; Проверка границы LGetSubNext SN1, Fld1, ebx, ExErr_NoSubItem, edx ; Переход к вхождению Запись в область данных элемента списка может производиться как mov [Reg].поле_структуры, данные Обращение к пользовательской части заголовка элемента списка или блока: mov [Reg-L?&SN&?ExtHd].поле_структуры, данные Запись в область данных головной структуры может производиться как mov [указатель_на_головной_элемент].LUsD.поле_структуры_пользователя, данные указатель_на_головной_элемент -- метка или ссылка в базовом регистре. Прежде, чем использовать кучу в программе, должен быть вызван макрос LInitHeap. Примеры вызова макроса LInitHeap MainStruc, ebx, 10240 ; Установить кучу размером 10 кБ, в месте, на кторое ; указывает ebx, с основной структурой MainStruc LInitHeap MainStruc, MainAddr, ebx,, 4 ; Установить по метке MainAddr кучу с головной структурой ; MainStruc, в которой первые четыре указателя устанав- ; ливаются пустыми. Размер кучи указан в ebx Перед использованием операций над элементами списков и подсписков (переходы от одно- го элемента к другому, выделение и возврат памяти и т. п.), должны быть определены типы списков и подсписков с помощью макросов LDefType и LDefSubType. Примеры вызова этих мак- росов LDefType StrucName1, 1,, circle ; Определяет однонаправленный циклический список со структурой ; фиксированного размера StrucName1 LDefType StrucName2, 2, variable ; Определяет двунаправленный линейный список со структурой ; StrucName2. Отдельные элементы этого списка могут иметь ; различный размер. LDefSubType StrucName2, FldPoint, 1,, ItsTable ; Указывает, что поле FldPoint структуры ; StrucName2 -- таблица ссылок на элементы ; (тип Б или В), образующие однонаправленные ; линейные списки. Память выделяется под элемент списка (или блок динамической памяти) по вызову макроса LGetEl (или LGetBlock) и в дальнейшем изменяться не может, только, если элемент удаляется макросом LFreeEl (или для блоков LFreeBlock. Эти макросы вызывают процедуры LGetmem и LFreemem, которые должны быть описаны в программе пользователя. В простейшем случае описание должно выглядеть следующим образом LGetmem proc LFreemem proc push eax push eax push edx push edx LGetm LBMN, ErGet,OKget push edi OKGet: clc ; Память выделена LFreem LBMN pop edx pop edi pop eax pop edx ret pop eax ErGet: add esp, 8 ret stc ; Сообщить об ошибке endp ret endp где LGetm, LFreem-- макросы, уже описанные в данном файле. Примеры использования некоторых макросов: Выделить память под элемент. LGetEl StrucName1 LGetEl StrucName2, SizeStrucName ; где SizeStrucName -- размер дополнительной ; ОЗУ, выделяемой после структуры списка Включение и исключение элементов из списка выполнют, соответственно, макросы LIncEl и LExcEl. LIncEl StrucName1 ; StrucName1 -- имя типа элемент, ebx -- вставляемый элемент ; ecx -- элемент списка, после которого нужно вставить элемент ebx Включение и исключение элементов из многомерных списков (тип В) выполнют, соответ- ственно, макросы LIncSubEl и LExcSubEl LExcSubEl StrucName2, FldPoint, edi ; StrucName2 -- имя типа элемент ; ebx -- исключаемый элемент ; edx -- элемент списка, после которого нужно исключить элемент ebx ; FldPoint -- Имя поля, которое является массивом ссылок ; edi -- регистр, содержащий индекс интересующей ссылки в массиве FldPoint. Если поле указывает на подсписок, то ссылка в него включается с помощью команды mov [reg_1].имя_поля, reg_2 где reg_2 -- указывает на элемент подсписка, который надо включить. Переходы от одного элемента к другому осуществляются с помощью макросов LGetNext и LGetPred (последний может использоваться только для двунаправленных списков): LGetNext ebx, StrucName1 ; ebx указывает на элемент StrucName1. Макрос осуществляет ; переход к следующему элементу StrucName1 LGetPred ebx, StrucName2, ErrHeap ; ebx указывает на элемент StrucName2. Макрос осуществляет ; переход к предыдущему элементу StrucName2, или, если ; предыдущий элемент не существует, выход по метке ErrHeap Переходы от одного элемента к другому в многомерных и ветвящихся подсписках, а также вход в иерархические подсписки осуществляются с помощью макросов LGetSubNext и LGetSubPred (последний может использоваться только для двунаправленных подсписков): LGetSubNext ebx, StrucName2, FldPoint, ErrHeap, edx ; ebx указывает на элемент StrucName2. ; Макрос осуществляет переход к элементу подсписка, ; на который указывает FldPoint, или, если предыдущий ; элемент не существует, выход по метке ErrHeap Для контроля попадания LChckInd StrucName2, FldPoint, ebx, edx, dl, ExErr Замечание. После входа в подсписок (тип Б) перемещения внутри него должны происходить с помощью мак- росов LGetNext и LGetPred. В списках типа "В" -- с помощью LGetSubNext и LGetSubPred. В ряде случаев целесообразно создавать список ещё на этапе трансляции программы. Например, если разрабатывается интерпретатор, то на момент начала работы ему уже должен быть известен определённый набор имён функций, который по мере обработки текста программы может постепенно пополняться новыми терминами. Формирование списков на этапе трансляции обеспечивается макросами LDeclareBegin, LDeclare и LDeclareEnd. Данные макросы отвечают за формирование заголовков элементов списка. Между ними должны следовать директивы аналогичные db,которые заполняют данными область дополнительного заголовка и область данных пользователя. Пример испльзования макросов ____________________________________ LDeclareBegin SN, L1 ; Объявление начала списка ; SN -- тип списка, определённый ранее макросом LDefType ; L1 -- метка начала списка LDeclare ; Объявление начала первого элемента dd ; Заполнение структуры пользовательской части заголовка (если она предусмотрена в определении типа) . . ; Заполнение структуры данных SN . dd ; Заполнение дополнительного блока памяти (если он предусмотрен в определении типа элемента) LDeclare ; Объявление начала следующего элемента dd ; Заполнение пользовательской части заголовка . ; Заполнение структуры и дополнительной области памяти . dd LDeclare L2 ; Объявление начала следующего элемента ; L2 -- метка, по которой можно обращаться к данному элементу dd ; Заполнение пользовательской части заголовка . ; Заполнение структуры и дополнительной области памяти . dd LDeclareEnd ; Объявление окончания списка ──────────────────────────────────── Вложенность и пересечение списков LDeclareBegin...LDeclareEnd не допускается. Если между двумя вызовами LDeclareBegin не стоит LDeclareEnd, то выдаётся сообщение об ошибке. Метки L1 и L2 относятся к началу области пользовательских данных элемента, поэтому записи mov ebx, offset L1 mov ebx, offset L2 LGetNext ebx, SN LGetNext ebx, SN дадут правильный результат. Макросы LDeclareBegin, LDeclare, LDeclareEnd используют следующие внутренние переменные ULLst -- счётчик элементов списка; OLheads -- размер заголовка элемента; DLdirs -- число направлений в списке (1 или 2); DLsize -- определяет, нужно ли формировать поле размера (значение Yes /1/-- нужно; No /0/-- нет); DLcirc -- указывает, является ли список циклическим (значение Yes /1/ -- циклический; No /0/-- нециклический); SLMin -- минимальный размер элемента, для элементов переменного размера (эта величина равна размеру заголовка, но не менее 8 байт, для элементов постоянного размера эта величина равна сумме размеров заголовка, структуры данных и дополнительного блока памяти; LElPos0 -- ссылка (значение счётчика $) на первый элемент списка; LElPos1 -- ссылка (значение счётчика $) на предыдущий сформированный элемент списка; LElPos -- ссылка (значение счётчика $) на формируемый элемент списка. Все эти переменные переопределяются при каждом вызове LDeclareBegin. Значение ULLst используется для слежения с правильностью использования макросов LDeclareBegin, LDeclare, LDeclareEnd. Если при вызове LDeclareBegin значение ULLst неотрицательно, то это воспринимается, как попытка создать вложенный список, и выдаётся сообщение об ошибке. Если при вызове LDeclare (или LDeclareEnd) значение ULLst отрицательно (не положительно), то выдаётся сообщение об ошибке, т. к. это воспринимается как попытка продолжить спиок, который уже закончился (или как попытка закончить пустой список). Остальные переменные автоматически создаются и используются только внутри групп LDeclareBegin...LDeclareEnd. И вне этих групп им можно присваивать любые значения. ~ ;**************************************************************** ;***************** Используемые переменные ********************** ;**************************************************************** ; L?&SN&?Def - переменная указывает, что стуктура SN определена для использования макросами ; файла, если её значение больше 0 (1 -- список, 2 -- блок, могут быть и другие); ; Следующие переменные формируются вызовом макроса LDefType ; L?&SN&?Def - присваивается значение 1; ; L?&SN&?dir - число направлений в списке данного типа SN (Значение 1 или 2) ; L?&SN&?Siz - размер элемента списка данного типа с учётом размера заголовка ; (число или variable) ; L?&SN&?Crc - кольцевой или линейный список (Yes или No) ; L?&SN&?Hd - переменная, указывающая размер заголовка элемента списка. ; L?&SN&?ExtHd - переменная, указывающая размер пользовательской части заголовка элемента списка. ; Следующие переменные формируются вызовом макроса LDefBlockType ; L?&SN&?Def - присваивается значение 2 ; L?&BlockName&?Siz - Размер блока данного типа BlockName ; L?&BlockName&?Hd - Размер заголовка блока данного типа BlockName ; L?&SN&?ExtHd - Переменная, указывающая размер пользовательской части заголовка блока. ; Следующие переменные формируются вызовом макроса LDefSubType ; L?&SN&?&Fld&?dir --- Число направлений в данном подсписке (Тип В =1 или 2, ; тип Б всегда =1) ; L?&SN&?&Fld&?Crc --- Указывает, является ли подсписок кольцевым (Тип В Yes или No, ; тип Б всегда =No) ; L?&SN&?&Fld&?ItsTable --- Указывает, что поле явлется таблицей из нескольких ссылок ; L?&SN&?&Fld&?LTabl --- Размер таблицы ссылок, при L?&SN&?&Fld&?ItsTable=2 (число вхождений>0 ; или variable, если количество вхождений определяется динамически). ; Здесь и далее ; SN - имя структуры, описывающей элемент списка данного типа. ; BlockName - имя типа блока, оно может быть именем какой-либо структуры, или не быть. ; Fld - Имя поля структуры SN или BlockName, которое используется как ссылка (или таблиц ; ссылок) на подсписок или следующий элемент многомерного списка. Вместо поля может ; быть указано смещение поля (число). ; LMSN - имя головной структуры ;**************************************************************** ; При выделении памяти перед каждой структурой SN в списке вставляется заголовок (одна из структур ; L1Dir или L2Dir в зависимости от числа направлений списка -- 1 или 2). Поле LSiz1 (или LSiz2) не ; используется, и память под него не выделяется, если список должен состоять из элементов фиксиро- ; ванного размера. Размер, заносимый в поле LSiz1 или LSiz2, вычисляется как сумма размеров заголов- ; ка, структуры данных SN и дополнительного блока памяти, размер которого может изменяться от одно- ; го элемента к другому. Размер заголовка может быть дополнительно расширен на величину ExtHead. L1Dir struc ; Заголовок элемента однонаправленного списка ┌───────┐ Lnext dd (nill) ; Ссылка на следующий элемент списка │Lnext │ LSiz1 dd (0) ; Размер данного элемента списка, ├───────┤ ends ; включая заголовок и (если есть) │LSiz1 │ ; дополнительной памяти └/\/\/\/ ; L2Dir struc ; Заголовок элемента двунаправленного списка ┌───────┐ Lnext dd (nill) ; Ссылка на следующий элемент списка │Lnext │ Lpred dd (nill) ; Ссылка на предыдущий ├───────┤ LSiz2 dd (0) ; Размер данного элемента списка, │Lpred │ ends ; включая заголовок и (если есть) ├───────┤ ; дополнительной памяти │LSiz2 │ ; └/\/\/\/ ; Заголовок блока выделенной памяти переменного размера. Размер заголовка может быть дополнительно ; расширен на величину ExtHead LBlck struc ; ┌───────┐ LSiz0 dd (0) ; Размер данного блока, │LSiz2 │ ends ; включая заговок └/\/\/\/ ; Структура элемента свободной памяти. LFree struc Lnext dd (?) LSiz1 dd (?) ends ; Головной элемент -- область статических переменных, предназначенная для хранения указа- ; теля на список свободной памяти, размера памяти, выделенной под кучу, указателей на пер- ; вые элементы списков и каких-нибудь иных статических данных нужных программисту. Ниже ; приведён заголовок головного элемента, с которого начинается любой головной элемент. LMainS struc ; Заголовок головного элемента LSizM dd (?) ; Размер головного элемента LSizMem dd (?) ; Объём выделенной под кучу памяти (общий) LFirstFree dd (?) ; Указатель на первый элемент списка свободной памяти LZero dd (0) ; Поле, используемое для упрощения алгоритма процедуры выделения памяти ends ; Начало области данных пользователя ;**************************************************************** ;************************* Макросы ****************************** ;**************************************************************** ; Вспомогательные макросы, которые выводят сообщения об ошибках, ; если не определен тип элемента списка или подтип. _LIsItDefType macro InputName errifndef L?&InputName&?Def "Тип элемента списка или блока не определён" errif L?&InputName&?Def LT 1 "Тип элемента списка или блока не определён" endm _LIsItDefTypeEl macro InputName errifndef L?&InputName&?Def "Тип элемента списка или блока не определён" errif L?&InputName&?Def LT 1 "Тип элемента списка или блока не определён" errif L?&InputName&?Def NE 1 "Тип определён не как элемент списка" endm _LIsItDefTypeBlk macro InputName errifndef L?&InputName&?Def "Тип элемента списка или блока не определён." errif L?&InputName&?Def LT 1 "Тип элемента списка или блока не определён" errif L?&InputName&?Def NE 2 "Тип определён не как блок." endm _LIsItDefTypeSubEl macro InputName, SubName _LIsItDefType InputName errifndef L?&InputName&?&SubName&?Crc "Тип подсписка не определён" ; Для любого подсписка определена переменная L?&InputName&?&SubName&?Crc endm ══════════════════════════════════════════════════════════════════════════════════════ ; Макросы, описанные в данном файле, предполагают, что каждый список состоит из однотипных элемен- ; тов, имеющих заголовок, структуру данных SN, и (необязательно) дополнительную область памяти, раз- ; мер которой может меняться от элемента к элементу. Тип описывается с помощью макроса LDefType. ; Этот макрос создаёт переменные с именами L?&SN&?dir, L?&SN&?Siz, L?&SN&?Hd, L?&SN&?ExtHd, и L?&SN&?Crc, ; которые в дальнейшем используются другими макросами для операций над элементами, а также определе- ; ния оптимального набора команд с элементами данного списка. SN является и именем структуры данных, ; используемых программистом, и именем типа списка. Если требуется создать списки с разным способом ; доступа к отдельным элементам (например, циклический однонаправленный и линейный двунаправленный) ; с одинаковой структурой данных, то следует описать её в двух разных структурах данных SN. При опи- ; сании можно использовать директиву equ: ; ; sn1 struc ; . ; . ; ends ; sn2 equ sn1 ; ; Далее, можно описать два разных типа SN1 и SN2. В памяти может создаваться несколько списков с ; одинаковой структурой данных SN и одинаковым методом доступа. Заголовок элемента формируется на ; основе значений числа направлений в списке Dir и величины дополнительной неструктурированной памя- ; ти Addmem. ; Вход: ; SN -- Имя структуры данных, которое означает и тип списка. Обязателен. ; Direct -- Число направлений (1 или 2). Обязателен. ; Addmem -- Размер памяти, добавляемый к размеру структуры SN. Если указано значение variable, то ; формируется элемент с переменным размером, если указано число или параметр опущен, то ; -- с фиксированным. Необязателен. ; Circle -- указывает, что список является циклическим, если опущен - линейным. Необязателен. Необя- ; зателен, отключает проверку существования следующего элемента. ; ExtHd -- размер дополнительной памяти в заголвоке (константа). Если в дополнительном заго- ; ловке планируется размещать структуру SNh, то вместо ExtHd надо написать size SNh. Не- ; обязателен. LDefType macro SN:Req, Direct:Req, Addmem:=<0>, Circle, ExtHd:=<0> if Direct NE 1 ; Если число направлений не верно (не два или не одно), то ошибка errif Direct NE 2 "Неверно указано или опущено число направлений списка. (Должно быть 1 или 2)" endif L?&SN&?dir=Direct ; Присваивает переменной число направлений ;────────────────────────────────────────────────────────────────── ifidni , ; Размер дополнительной памяти -- переменный L?&SN&?Hd=Direct*4+ExtHd+4 ; вычисляет размер заголовка = ; = число направлений + размер структуры (по 4 байта на поле)+ ; + размер дополнительного заголовка L?&SN&?Siz=variable ; размер элемента списка - переменный else ; Размер дополнительной памяти -- фиксированный L?&SN&?Hd=Direct*4+ExtHd ; Вычисляет размер заголовка элемента. L?&SN&?Siz=L?&SN&?Hd+Addmem+size SN ; размер элемента списка = ; размер заголовка+размер структуры+дополнительная память if L?&SN&?Siz LT 8 ; Если размер элемента меньше 8, то, чтобы корректно работали L?&SN&?Siz=8 ; операции выделения и возврата в кучу памяти, положим размер ; элемента равным 8 %out "Размер элемента меньше 8 байт. Дополнен до 8 байт" endif endif ;────────────────────────────────────────────────────────────────── ifb ; Устанавливает знак, указывающий, что список явля- L?&SN&?Crc=No ; ется линейным else ; L?&SN&?Crc=Yes ; Список является кольцевым. endif ; L?&SN&?ExtHd=ExtHd ; Устанавливает размер дополнительной памяти L?&SN&?Def=1 ; Указывает, что тип определен как список endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Следующие макросы предназначены для формировании в сегменте данных готовых списков ; Вызов ; LDeclareBegin SN:req, ListN:req ; LDeclare ListN1 ; LDeclareEnd ; Вход: ; SN -- имя типа структуры (обязателен); ; ListN -- имя метки, по которой можно обращаться к первому элементу списка (обязателен); ; ListN1 -- имя метки элемента, по которой можно обращаться к конкретному элементу списка (не обязателен). LDeclareBegin macro SN:req, ListN:req ifdef ULLst errif ULLst GE 0 "Нельзя начинать формировать новый список, пока не закончился старый" endif ULLst =0 ; Пометить, что список начался OLheads =L?&SN&?Hd ; Размер заголовка DLdirs =L?&SN&?dir ; Число направлений if L?&SN&?Siz EQ variable ; Нужно ли поле размера DLsize =Yes else DLsize =No endif DLcirc =L?&SN&?Crc ; Циклический список? SLMin =L?&SN&?Hd+L?&SN&?Siz ; Минимальный размер элемента ; Минимальный размер элемента никогда не будет меньше 8 байт, т. к., если заголовок элемента содержит ; поле размера и поле ссылки на следующий элемент, то его размер, даже без пользовательских данных ; будет не меньше 8 байт, а если заголовок содержит только ссылку на следующий элемент, то о том, чтобы ; его размер был не меньше 8 байт позаботится макрос LDefType LElPos0 = $ ; Ссылка на первый элемент org LElPos0+OLheads; Сформировать символическое имя первого элемента ListN dd (?) org LElPos0 ; Вернуться назад endm LDeclare macro ListN1 errifndef ULLst "В начале должен быть вызван макрос LDeclareBegin" errif ULLst LT 0 "В начале должен быть вызван макрос LDeclareBegin" ULLst = ULLst+1 LElPos=$ if DLsize EQ No ; Когда под размещение элемента требуется меньше восьми байт, макрос LDefType доводит требуемый объём ; до восьми байт (устанавливает L?&SN&?Siz=8). Чтобы в таком случае пользователь не заботился о вставке ; лишних директив db, макрос LDeclare дополняет объём элемента до 8 байт, (когда SLMin равно 8 байт). if SLMin EQ 8 if LElPos-LElPos1 LT 8 LElPos = LElPos+8-(LElPos-LElPos1) endif endif endif ifnb ; Создать метку, если задана org LElPos+OLheads ListN1 dd (?) org LElPos endif if ULLst GT 1 ;_________________________ org LElPos1 ;*** Формирование ссылок в предыдущем элементе *** dd LElPos+OLheads ; Ссылка в прошлом элементе на следующий if DLdirs EQ 2 ;; Пропустить ссылку на предыдущий элемент (если список двунаправленный) org $+4 endif if DLsize EQ Yes ;; Если список переменного размера, то запомнить размер предыдущего элемента errif LElPos-LElPos1 LT SLMin "Объём данных в определении списка меньше заголовка." dd LElPos-LElPos1 ;; размер элемента else errif LElPos-LElPos1 GT SLMin "Данных слишком много для формирования элемента фиксированного размера" errif LElPos-LElPos1 LT SLMin "Данных слишком мало для формирования элемента фиксированного размера" endif endif ;_________________________ org LElPos ;*** Формирование ссылок в текущем элементе *** if DLcirc EQ No ; Ссылка на следующий элемент dd nill ;; Для нециклического списка она пустая else dd LElPos0+OLheads ;; Для циклического списка она указывает на первый элемент endif if DLdirs EQ 2 ; Ссылка на предыдующий элемент if ULLst NE 1 dd LElPos1+OLheads ; Если не первый элемент, то запомнить ссылку на предыдущий элемент else dd nill endif endif if DLsize EQ Yes ; Если есть поле размера элемента, то пропустить его dd (?) endif ;_________________________ LElPos1=LElPos; endm LDeclareEnd macro errifndef ULLst "Список не определён. Вначале должен быть вызван LDeclareBegin." errif ULLst EQ 0 "Между LDeclareBegin и LDeclareEnd должен быть вызван по крайней мере один раз макрос LDeclare" errif ULLst LT 0 "Список не определён. Должен быть вызван LDeclareBegin." ULLst = -1 ; Пометить, что список закончился LElPos = $ if DLsize EQ No ;; Дополнение размера элемента до восьми байт if SLMin EQ 8 ;; if LElPos-LElPos1 LT 8 LElPos = LElPos+8-(LElPos-LElPos1) endif endif endif errif LElPos-LElPos1 LT SLMin "Объём данных в определении списка сишком мал." if DLcirc EQ Yes if DLdirs EQ 2 ; Определяет в первом элементе ссылку на последний org LElPos0+4 dd LElPos1+OLheads endif endif if DLsize EQ Yes ; Устанавливает размер элемента if DLdirs EQ 2 org LElPos1+8 else org LElPos1+4 endif dd LElPos-LElPos1 endif org LElPos endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Данный макрос определяет тип ссылок на элементы подсписков для типов подсписков Б и В. ; Вход: ; SN -- Имя структуры данных, которое означает и тип списка, для которого ; определяется подсписок. Обязателен ; Direct -- Число направлений (1 или 2 для списков типа В и EnterSubList для типа Б). Обязателен. ; Fld -- Имя поля структуры SN, которое используется как ссылка на подсписок или ; следующий элемент многомерного списка. Обязателен ; Circle -- указывает, что многомерный список является циклическим, если опущен - линей- ; ным. Необязателен, отключает проверку существования следующего элемента. ; ItsTable -- Указывает (если есть), что поле является таблицей ссылок. Необязателен. При ; использовании LapTablS= допустимы следующие значения: , ; и , указывающие соответственно, что поле размера является байтом, словом или ; двойным словом. ; LapTablS -- Число ссылок в таблице. Если параметр не указан, то проверка, попадает ли индекс ; в таблицу ссылок, не выполняется. Если указано нулевое значение, то считается, что ; размер таблицы задаётся байтом, предшествующим, таблице (что вносит ограничение -- ; 256 ссылок одно- или двунаправленных при использовании таблиц переменного размера ; //значения индексного регистра могут лежать в пределах IReg=0...255, 0 соответствует ; первой ссылке//. Таблицы постоянного размера могут содержать большее число ссылок). LDefSubType macro SN:Req, Fld:Req, Direct:Req, Circle, ItsTable, LapTablS _LIsItDefType SN ; Если тип не определён, то выдать сообщение об ошибке ifdifi , if Direct NE 1 ; Если число направлений не верно, то ошибка errif Direct NE 2 "Неверно указано или опущено число направлений списка. (Должно быть 1 или 2)" endif endif ifdifi , ; Присваивает переменной число направлений L?&SN&?&Fld&?dir=Direct else L?&SN&?&Fld&?dir=1 ; Ссылки в подсписок всегда осуществляются в одну сторону ; С помощью макросов перехода к следующему и предыдущему endif ; элементам из подсписка (типа Б) в список попасть нельзя. ;────────────────────────────────────────────────────────────────── ifdifi , ; Присваивает переменной число направлений ifb ; Устанавливает знак, указывающий, что подсписок явля- L?&SN&?&Fld&?Crc=No ; ется линейным else L?&SN&?&Fld&?Crc=Yes ; Подсписок является кольцевым. endif else L?&SN&?&Fld&?Crc=No ; Элементы подсписка не могут ссылаться на элементы endif ; списка, по этому они не могут быть кольцевыми ;────────────────────────────────────────────────────────────────── ifb ; Устанавливает признак, что L?&SN&?&Fld&?ItsTable=No ; поле не является массивом ссылок elseifb L?&SN&?&Fld&?ItsTable=1 ; Поле является массивом ссылок, проверка индексов не выполняется else ifidni , L?&SN&?&Fld&?ItsTable=2 ; Поле является массивом ссылок c размером, задаваемым бай- L?&SN&?&Fld&?LTabl=LapTablS ; том, предшествующим массиву ссылок, если LapTablS=variable, ; или массивом ссылок, с фиксированным размером, если ; LapTablS<>variable. Проверка индекса должна выполняться. elseifidni , L?&SN&?&Fld&?ItsTable=3 ; Поле является массивом ссылок c размером, задаваемым бай- L?&SN&?&Fld&?LTabl=LapTablS ; том, предшествующим массиву ссылок, если LapTablS=variable, ; или массивом ссылок, с фиксированным размером, если ; LapTablS<>variable. Проверка индекса должна выполняться. elseifidni , L?&SN&?&Fld&?ItsTable=4 ; Поле является массивом ссылок c размером, задаваемым бай- L?&SN&?&Fld&?LTabl=LapTablS ; том, предшествующим массиву ссылок, если LapTablS=variable, ; или массивом ссылок, с фиксированным размером, если ; LapTablS<>variable. Проверка индекса должна выполняться. else %out "Допустимые значения параметра ItsTable" %out " -- размер поля числа ссылок в массиве равен байту" %out " -- размер поля числа ссылок в массиве ссылки равен слову" %out " -- размер поля числа ссылок в массиве ссылки равен двойному слову" .err endif endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Проверка попадания индекса в пределы таблицы ссылок ; PReg -- регистр, указывающий на элемент; ; IReg -- индекс (регистр) в таблице ссылок; ; LIReg -- младший байт индексного регистра, нужен только когда массив ссылок имеет ; переменный размер; ; ExErr -- метка, по которой должен осуществляться переход, если индекс выходит за ; границу таблицы; ; Изменяемые регистры: Flags ; Особенности работы: Если ExErr не указан, то после выполнения макроса флаги установлены ; таким образом, что по команде 'ja xxx' будет выполнен переход в том случае, если индекс выходит ; за пределы таблицы, а по команде 'jbe xxx', если не выходит. Если размер памяти, отводимой под ; таблицу ссылок, выделяется динамически, т. е. перед таблицей имеется поле размера, и метка ExErr ; не указана, то проверке подвергается только младший байт регистра, что может быть использовано в ; тех случаях, когда заведомо известно, что индекс помещённый в IReg не превышает 255 (например, ; когда индекс заносится командой movzx). LChckInd macro SN:REQ, Fld:REQ, Reg:=, ExErr, IReg:REQ, LIReg:REQ _LIsItDefTypeSubEl SN, Fld errif L?&SN&?&Fld&?ItsTable EQ No "Данное поле не является таблицей ссылок!" if L?&SN&?&Fld&?ItsTable EQ 2 if L?&SN&?&Fld&?LTabl NE variable cmp LIReg, L?&SN&?&Fld&?LTabl-1 else ifnb test IReg, 0FFFFFF00h jnz ExErr endif cmp LIReg, [Reg].[Fld-1] endif ifnb ja ExErr endif elseif L?&SN&?&Fld&?ItsTable EQ 3 if L?&SN&?&Fld&?LTabl NE variable cmp LIReg, L?&SN&?&Fld&?LTabl-1 else ifnb test IReg, 0FFFF0000h jnz ExErr endif cmp LIReg, [Reg].[Fld-2] endif ifnb ja ExErr endif elseif L?&SN&?&Fld&?ItsTable EQ 4 if L?&SN&?&Fld&?LTabl NE variable cmp LIReg, L?&SN&?&Fld&?LTabl-1 else cmp LIReg, [Reg].[Fld-4] endif ifnb ja ExErr endif endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Макрос предназначен для определения типа блока, чтобы можно было использовать ссылки на ; другие подсписки (при совместном использовании с LDefSubType) и для автоматизации выделения ; памяти ; BlockName -- имя типа блока. Обязателен ; BlockSize -- размер блока или variable. Необязателен. Если параметр не указан или равен ; variable, то подразумевается переменный размер блока. ; ExtHd -- размер дополнительной памяти в заголовке (константа). Если в дополнительном ; заголовке планируется размещать структуру SN2, то вместо ExtHd надо написать ; size SN2. Необязателен. LDefBlockType macro BlockName:REQ, BlockSize:=, ExtHd:=<0> ifdifi , L?&BlockName&?Siz =BlockSize+ExtHd; if L?&BlockName&?Siz LT 8 ; Если размер элемента меньше 8, то, чтобы корректно работали L?&BlockName&?Siz=8 ; операции выделения и возврата в кучу памяти, положим размер endif ; элемента равным 8 L?&BlockName&?Hd=0 elseifidni , L?&BlockName&?Siz =variable; L?&BlockName&?Hd=ExtHd+size LBlck endif L?&BlockName&?ExtHd=ExtHd L?&SN&?Def=2 ; Указывает, что тип определен как список endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Следующий макрос формирует головной элемент в памяти, устанавливает список свободных элементов (см. ; рис. 1), и устанавливает пустые ссылки в первых Fnill элементах структуры SN. ; SN -- Имя головной структуры, размещаемой за заголовком. Необязателен. ; LBMN -- База головного элемента, метка или [регистр]. Должен быть или меткой, или индексным ; регистром. Сложный тип (например, Label1.[ebx]) недопустим. Параметр обязателен. ; SHeap -- Общая память, выделенная под кучу, включая заголовок. Константа или регистр, лучше edi. Обязателен. ; SizExt -- Размер дополнительной части головной структуры не входящей в SN. Необязателен. Константа или регистр. ; Fnill -- Число первых двойных слов SN которые следует заполнить значением nill. Может быть полезно в том ; случае, когда нужно несколько первых указателей сделать пустыми при установке кучи. ; Необязателен. Константа или регистр (лучше ecx). ; SFree -- Число пропускаемых байт между головной структурой и первым элементом списка свободной памяти. Это ; может потребоваться, например для создания некоторых элементов списков при настройке кучи. Память, ; выделяемая по SFree может быть потом перемещена в список свободных элементов. Необязателен. Константа. ; eax -- для предачи параметров не использовать. ; Портятся слдующие регистры: -- eax, edi; ; -- при наличии Fnill>0 ECX. ; При использовании FNill ES и DS должны указывать на один и тот же сегмент. LInitHeap macro SN, LBMN:REQ, SHeap:REQ, SizExt:=<0>, Fnill, SFree:=<0> local ffr, LMainSize, Reg?, SizExtIsReg? mov [LBMN].LSizMem, SHeap ; Заносит в головной элемент полный размер памяти, выделенный под кучу. ;────────────────────────────────────────────────────────────── ; Вычисляем размер головной структуры: IsItReg?d SizExt, SizExtIsReg? ifb LMainSize= size LMainS ; Размер головного элемента = Размер заголовка + else ; + [Размер структуры пользователя] + LMainSize= size SN + size LMainS endif ; Заносит в головной элемент его собственный размер if SizExtIsReg? EQ No mov [LBMN].LSizM, LMainSize+SizExt else lea eax, [SizExt]+LMainSize mov [LBMN].LSizM, eax endif ;────────────────────────────────────────────────────────────── ; Определяет смещение относительно базы головного элемента, по которому следует разместить ; первый элемент списка свободной памяти. ffr=SFree+LMainSize ; первый свободный=размер головного+ [участок, первоначально не ; включаемый в список свободной памяти, если нужен] ;────────────────────────────────────────────────────────────── ; Устанавливает указатель в головной структуре на первый элемент списка свободной памяти. IsItReg?d LBMN, Reg? if Reg? EQ Yes lea eax, [LBMN].[SizExt]+ffr mov [LBMN].LFirstFree, eax elseif SizExtIsReg? EQ Yes lea eax, [LBMN].[SizExt]+ffr mov [LBMN].LFirstFree, eax else mov [LBMN].LFirstFree, (offset LBMN) + ffr + SizExt endif xor eax, eax ; Заносим в поле размера фиктивного элемента списка mov [LBMN].LZero, eax ; свободной памяти нулевой размер ;─────────────────────────────────────────────────────────────── ; Сформируем первый элемент списка свободной памяти dec eax ; Помещает пустую ссылку в eax mov [LBMN].[SizExt].Lnext+ffr, eax ;(nill) ; Следующего элемента в списке нет. ; Размер свободного элемента -- вся память, не вошедшая в головную структуру и не зарезервированная после неё. IsItReg?d SHeap, Reg? if Reg? EQ No if SizExtIsReg? EQ Yes mov [LBMN].LSiz1.[SizExt]+ffr, SHeap-ffr sub [LBMN].LSiz1.[SizExt]+ffr, SizExt else mov [LBMN].LSiz1.[SizExt]+ffr, SHeap-ffr-SizExt endif else if SizExtIsReg? EQ Yes movreg edi, SHeap lea eax, [SizExt]+ffr sub edi, eax mov [LBMN].LSiz1.[SizExt]+ffr, edi else mov [LBMN].LSiz1.[SizExt]+ffr, [SHeap] sub [LBMN].LSiz1.[SizExt]+ffr, SizExt+ffr endif endif ;─────────────────────────────────────────────────────────────── ; Если несколько первых элементов в SN в начале должны иметь пустые ссылки, то заполрним их ifnb movreg ecx, fnill ; Устанавливает число пустых ссылок, которые надо записать lea edi, LBMN+size LMainS ; заносить пустые ссылки, в ES:DI. cld ; Устанавливает направление записи вперёд rep stosd ; Запись пустых ссылок (двойных слов) endif endm ;══════════════════════════════════════════════════════════════════════════════════ ; Следующие макросы описывают переходы к следующему элементу списка типа А (LGetNext), ; а также к входу в подсписок типа Б и к переходу к следующему элементу многомерного ; списка типа В (LGetSubNext). ; Вход: ; SN - имя структуры (тип элемента) ; [Reg] - указатель на начало элемента, по умолчанию [ebx] ; exer -- метка, на которую переходит макрос в случае, если следующий элемент ; не существует. Нужен только для линейных списков. ; Выход: [Reg] -- новый элемент, или переход по метке, exer если следующий элемент ; отсутствует. В последнем случае значение [Reg] не изменяется. ; Для макроса LGetSubNext должны дополнительно указываться следующие параметры ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld (числом). LGetnext macro SN:Req, Reg:=, exer:Req ; Переход к следующему элементу списка типа А local Reg? _LIsItDefTypeEl SN IsItReg?d Reg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" if L?&SN&?Crc EQ No ; Если список линейный, то проверить errifb "Для линейных списков должна обязательно указываться метка ExErr" cmp [Reg].Lnext-L?&SN&?Hd, nill ; является ли указатель на следующий je exer ; элемент пустым, и, если является, то выход с ошибкой endif mov Reg, [Reg].Lnext-L?&SN&?Hd ; Переход к следующему элементу endm LGetSubNext macro SN:Req, Fld:Req, Reg:=, exer:Req, IReg ; Переход к следующему local Reg? ;элементу подсписка (для типов Б и В) _LIsItDefTypeSubEl SN, Fld IsItReg?d Reg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" if L?&SN&?&Fld&?ItsTable EQ No if L?&SN&?&Fld&?Crc EQ No errifb "Для линейных подписков должна обязательно указываться метка ExErr" cmp dword ptr [Reg].&Fld&.Lnext, nill ; Указатель пустой? je exer ; Да - ошибка endif mov Reg, [Reg].&Fld&.Lnext ; Переход в подсписок else errifb "Для таблиц подсписков должен обязательно указываться параметр IReg" if L?&SN&?&Fld&?Crc EQ No errifb "Для линейных подсписков должна обязательно указываться метка ExErr" cmp dword ptr [Reg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lnext, nill ; Указатель пустой? je exer ; Да - ошибка endif mov Reg, [Reg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lnext ; Переход в подсписок endif endm ;════════════════════════════════════════════════════════════════════════───────── ; Следующие макросы осуществляют переход к предыдущему элементу, могут при- ; меняться только к двунаправленным спискам (LGetPred) и к двунаправленным многомерным ; спискам типа В (LGetSubPred). ; SN - имя структуры (тип элементов) ; [Reg] - указатель на начало элемента, по умолчанию [ebx] ; exer -- метка, на которую переходит макрос в случае, если следующий элемент не существует ; Выход: [Reg] -- новый элемент, или переход по метке exer, если предыдущий элемент не существует. ; В последнем случае значение [Reg] не изменяется. ; Для макроса LGetSubPred должны дополнительно указываться следующие параметры ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld (числом). LGetPred macro SN:Req, Reg:=, exer ; Переход к предыдущему элементу local Reg? ; списка (только для двунаправленного) _LIsItDefTypeEl SN IsItReg? Reg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" errif L?&SN&?dir EQ 1 "Невозможен переход к предыдущему элементу, т. к. список однонаправленный" if L?&SN&?Crc EQ No ; Если список линейный, то проверить errifb "Для линейных списков должна обязательно указываться метка ExErr" cmp [Reg].Lpred-L?&SN&?Hd, nill ; является ли указатель на предыдущий je exer ; элемент пустым и если является, то сообщение об ошибке endif mov Reg, [Reg].Lpred-L?&SN&?Hd ; Переход к предыдущему элементу endm LGetSubPred macro SN:Req, Fld:Req, Reg:=, exer, IReg ; Переход к предыдущему элементу под- local Reg? ; списка (только для типа В, причём двунаправленного) _LIsItDefTypeSubEl SN, Fld IsItReg?d Reg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" errif L?&SN&?&Fld&?dir EQ 1 "Невозможен переход к предыдущему элементу, т. к. список однонаправленный" if L?&SN&?&Fld&?ItsTable EQ No errifb "Для таблиц подсписков должен обязательно указываться параметр IReg" if L?&SN&?&Fld&?Crc EQ No ; Если список линейный, то проверить errifb "Для линейных многомерных списков должна обязательно указываться метка ExErr" cmp [Reg].&Fld&.Lpred, nill ; является ли указатель на предыдущий je exer ; элемент пустым и если является, то сообщение об ошибке endif mov reg, [Reg].&Fld&.Lpred ; Переход к предыдущему элементу else if L?&SN&?&Fld&?Crc EQ No ; Если список линейный, то проверить errifb "Для линейных многомерных списков должна обязательно указываться метка ExErr" cmp [Reg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred, nill ; является ли указатель на предыдущий je exer ; элемент пустым и если является, то сообщение об ошибке endif mov reg, [Reg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred ; Переход к предыдущему элементу endif endm ;═════════════════════════════════════════════════════════════════════════════════ ; Макросы вставки элементов в списки (LIncEl) и многомерные подсписки типа В (LIncSubEl) ; В списке (подсписке) до вставки должно присутствовать не меньше 1 элемента. ; SN -- Тип списка ; [ecx] -- элемент, после которого нужно вставить другой элемент ; [ebx] -- Элемент, который нужно вставить в список ; Портит регистр EAX ; Для макроса LIncSubEl должны дополнительно указываться следующие параметры ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ссылки ; в Fld (числом). ; Данный макрос не предназначен для работы с обычными подсписками (тип Б). Подсписки ; могут быть только многомерными (тип В). ; Портит eax ; ┌────┐ ┌────┐ [ecx] указывает на элемент "а" ; │ а │─────────────────>│в │ [ebx] указывает на элемент "б" ; │ │<─────────────────│ │ [eax] указывает на элемент "в" ; └────┘ ┌ ─>└────┘ Элемент "а" содержит ссылку на "в" ; ^ ^│ ┌────┐ ^ ^ Элемент "в" содержит ссылку на "а" (*) ; │ │ │ б │─ ─ ─┘ │ │ Элемент "б" не указывает ни на кого ; │ └─ ─>│ │  Чтобы вставить "б" в список, нужно ; │ └ ─ ─└────┘─ ─ ─ ─ ─ ┘ │ - в "б.сл" поместить ссылку на "в" ; │ ^ - в "а.сл" поместить ссылку на "б" ; │ │ │ - в "б.пр" поместить ссылку на "а" (*) ; [ecx] [ebx] [eax] - в "в.пр" поместить ссылку на "б" (*) ; ; Шаги (*) нужны для двунаправленных списков. LIncEl macro SN:Req ; Вставка элемента в список local skip _LIsItDefTypeEl SN mov eax, [ecx].LNext-L?&SN&?Hd ; eax <- указатель на "в" (ecx.cл) mov [ebx].LNext-L?&SN&?Hd, eax ; в "б.сл" <- ссылка на "в" mov [ecx].LNext-L?&SN&?Hd, ebx ; в "а.сл" <- ссылка на "б" if L?&SN&?dir EQ 2 ; Если число направлений равно 2, то mov [ebx].Lpred-L?&SN&?Hd,ecx ; в "б.пр" <- ссылка на "а" if L?&SN&?Crc EQ No ; Если список не циклический, то выполнить cmp eax,nill ; проверку, существует ли элемент "в" [eax] je skip ; если элемент "в" не существует, то пропустить вставку в него endif ; ссылки на "б" mov [eax].LPred-L?&SN&?Hd, ebx ; Установить в элементе "в.пр" ([eax]) ссылку на"б"([edx]). skip: endif endm LIncSubEl macro SN:Req, Fld:Req, IReg ; Вставка элемента в подсписок local skip _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?ItsTable EQ No mov eax, [ecx].&Fld&.LNext ; в eax <-- указатель на "в" mov [ebx].&Fld&.LNext, eax ; в "б.сл" <-- указатель на "в" mov [ecx].&Fld&.LNext, ebx ; в "а.сл" <-- указатель на "б" if L?&SN&?&Fld&?Dir EQ 2 mov [ebx].&Fld&.Lpred, ecx ; в "б.сл" <-- указатель на "а" if L?&SN&?&Fld&?Crc EQ No cmp eax, nill ; Элемент существует? je skip ; Нет -- выход endif mov [eax].&Fld&.LPred, ebx ; иначе в "в.пр" <-- указатель на "а" skip: endif else mov eax, [ecx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LNext ; в eax <-- указатель на "в" mov [ebx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LNext, eax ; в "б.сл" <-- указатель на "в" mov [ecx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LNext, ebx ; в "а.сл" <-- указатель на "б" if L?&SN&?&Fld&?Dir EQ 2 mov [ebx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred, ecx ; в "б.сл" <-- указатель на "а" if L?&SN&?&Fld&?Crc EQ No cmp eax,nill ; Элемент существует? je skip ; Нет -- выход endif ; иначе в "в.пр" <-- указатель на "а" mov [eax].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LPred, ebx skip: endif endif endm ;═════════════════════════════════════════════════════════════════════════════════ ; Макросы вставки элементов в списки (LExcEl) и многомерные подсписки типа В (LExcSubEl) ; В списке (подсписке) до вставки должно присутствовать не меньше 1 элемента. ; SN -- Тип списка ; [EBX] - указатель на исключаемый элемент ; [EDX] - указатель на предыдущий элемент списка. Необходим, если только список не является ; двунаправленным, иначе загружается в edx из элемента, на который указывает ebx. ; Если список - циклический, и элемент в нём - последний, то элемент исключён не будет ; Портится EAX, при работе с двунаправленными списками EDI ; в списке должно быть минимум два элемента ; Предыдущий элемент обязательно должен существовать, т. к. проверка его существования макросом ; не производится. ; Для макроса LExcSubEl должны дополнительно указываться следующие параметры. ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является массивом ссылок. Может быть также смещением ссылки в Fld (числом). ; Портит eax ; ; ┌────┐ ┌────┐ [ebx] указывает на элемент "а" ; │ а │─ ─ ─ ─ ─ ─ ─ ─ ─>│в │ [edx] указывает на элемент "б" ; │ │<─ ─ ─ ─ ─ ─ ─ ─ ─│ │ [eax] указывает на элемент "в" ; └────┘ ┌──>└────┘ элемент "а.сл" содержит ссылку на "б" ; ^ ^│ ┌────┐ │ ^ ^ элемент "б.сл" содержит ссылку на "в" ; │ ││ │ б │─────┘ │ │ элемент "б.пр" содержит ссылку на "а" (*) ; │ │└───>│ │ │ элемент "в.пр" содержит ссылку на "б" (*) ; │ └─────└────┘──────────┘ │ Чтобы исключить "б" из списка, нужно ; │ ^ - в "а.сл" поместить ссылку на "в" ; │ │ │ - в "в.пр" поместить ссылку на "а" (*) ; [edx] [ebx] [eax] ; ; шаги (*) нужны для двунаправленных списков. LExcEl macro SN:Req ; Исключить элемент из списка local skip _LIsItDefTypeEl SN if L?&SN&?dir EQ 2 ; Если список двунаправленный, то загрузить mov edx, [ebx].LPred-L?&SN&?Hd ; указатель на элемент "a" в edx endif mov eax, [ebx].LNext-L?&SN&?Hd ; Загружает в eax указатель на элемент "в". mov [edx].LNext-L?&SN&?Hd, eax ; Заносит в элемент "а.сл" ссылку на элемент "в" if L?&SN&?dir EQ 2 ; Если список двунаправленный, то if L?&SN&?Crc EQ No ; для нециклического списка выполнить cmp eax,nill ; проверку, существует ли элемент "в" (указывает eax) je skip ; если "в" не существует, то пропустить вставку в endif ; "в.пр" ссылки на элемент "а" mov [eax-L?&SN&?Hd].Lpred,edx ; в "в.пр" помещает ссылку на предыдущий "a" skip: endif endm LExcSubEl macro SN:Req, Fld:Req, IReg ; Исключить элемент из подсписка local skip _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?ItsTable EQ No ; if L?&SN&?&Fld&?Dir EQ 2 ; Если список двунаправленный, то mov edx, [ebx].&Fld&.LPred ; загрузить в edx указатель на элемент "а" endif mov eax, [ebx].&Fld&.LNext ; eax <-- указатель на "в" mov [edx].&Fld&.LNext, eax ; в "а.сл" <-- указатель на "в" if L?&SN&?&Fld&?Dir EQ 2 if L?&SN&?&Fld&?Crc EQ No ; Если список не циклический, то cmp eax,nill ; проверить, существует ли следующий элемент. je skip endif mov [eax].&Fld&.Lpred,edx ; в "в.сл" <-- ссылку на "а" skip: endif else if L?&SN&?&Fld&?Dir EQ 2 ; Если список двунаправленный, то mov edx, [ebx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LPred ; загрузить в edx указатель endif ; на элемент "а" mov eax, [ebx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LNext ; eax <-- указатель на "в" mov [edx].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].LNext, eax ; в "а.сл" <-- указатель на "в" if L?&SN&?&Fld&?Dir EQ 2 if L?&SN&?&Fld&?Crc EQ No ; Если список не циклический, то cmp eax,nill ; проверить, существует ли je skip ; следующий элемент. endif mov [eax].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred,edx ; в "в.сл" <-- ссылку на "а" skip: endif endif endm ;═════════════════════════════════════════════════════════════════════════════════ ; Ниже приведён макрос выделения памяти заданного размера. ; Важно! В принципе макрос позволяет выделить память объёмом в 1 байт, но возвращать ; в кучу память объёмом менее 8 байт опасно, т. к. при этом велика вероятность (около 100%) ; разрушения списковой структуры. ; Вход: ; LBMN -- метка или индексный регистр, в котором находится ссылка ; на головной элемент. ; ecx -- Размер необходимого блока памяти ; exer -- метка выхода для случая, когда блок памяти подходящего размера ; не найден ; OK -- метка выхода для случая, когда память выделена ; Выход: EBX -- указатель на выделенный блок памяти. ; Портятся регистры EAX, EDX. ; ; Данный макрос должен быть оформлен в программе как процедура LGetMem для использования ; макросов выделения памяти и в простейшем случае может выглядеть следующим образом ; ; LGetmem proc ; ; вход: ECX -- требумый размер блока памяти ; ; выход: EBX -- указывает на начало выделенного участка памяти ; CF=0 -- всё в порядке, CF=1 -- блок памяти не выделен. ; LGetm LBMN, ErGet,OKget ; OKGet: ; clc ; ret ; ErGet: ; stc ; ret ; endp LGetm macro LBMN:Req, exer:Req, OK:Req local rpt, nxt, gtm, exr lea edx, [LBMN].Lfirstfree ; В начале edx хранит ссылку на первый свободный элемент ; Далее хранит адрес предыдущего свободного элемента. mov ebx, [edx] ; Загружает указатель на первый свободный элемент, в ; дальнейшем ebx указывает на очередной проверяемый элемент cmp ebx, nill ; Если указатель пустой (нет свободной ОЗУ), то выход с ошибкой je exer ;──────────────────────────────────────────────────────────────────── rpt: mov eax, [ebx].LSiz1 ; проверить, достаточен ли размер данного свободного элемента ; для выделения требуемого объёма ОЗУ. sub eax, ecx ; eax содержит остаток в свободном элементе после выделения из него ; затребованного объёма ОЗУ ja gtm ; Если размер свободного элемента больше, чем требуется, ; (остаток eax > 0), то выделить память, если при этом не произойдёт ; затирания заголовка (остаток памяти не меньше заголовка) jb nxt ; Если меньше (остаток в eax отрицательный) - перейти к следующему ; элементу ;──────────────────────────────────────────────────────────────────── ; Если размер свободного элемента равен требуемому объёму ОЗУ (остаток в eax<0), ; то выделить весь блок памяти mov eax, [ebx].Lnext ; в предыдущий элемент поместить ссылку на следующий mov [edx].Lnext, eax jmp OK ; [edx] [ebx] eax=0 Содержимое рег-ов и ссылки в ; │ │ элементах свободной памяти ; v v до выделения памяти ; ┌────────────────┐┌─────────────────┐ ; │ v│ v ; ┌──┬──┬──────┐ ┌──┬──┬──────┐ ┌──┬──┬──────┐ ; │ │ │Св.Эл.│ │ │ │Св.Эл.│ │ │ │Св.Эл.│ ; └──┴──┴──────┘ └──┴──┴──────┘ └──┴──┴──────┘ ; │ │ ecx │ ^ ; │ │<──────────>│ │ ; └───────────────────────────────────┘ ; ^ ^ ^ Содержимое рег-ов и ссылки в ; │ │ │ элементах свободной памяти ; [edx] [ebx] [eax] после выделения памяти ; ;──────────────────────────────────────────────────────────────────── gtm: cmp eax, size Lfree ; Если из элемента свободной памяти нельзя выделить ; ОЗУ таким образом, чтобы осталось не меньше, чем jb nxt ; занимает заголовок (может произойти затирание заголовка), ; то перейти к следующему свободному элементу ; заголовок ; ──>│─────│<─── Случай, когда элемент по размеру больше, ; │ │ чем требуемый блок памяти, но память из него ; ┌──┬──┬──────┐ выделена быть не может, т. к. после выделения ; │ │ │Св.Эл.│ должен быть оставлен участок памяти хотя бы ; └──┴──┴──────┘ с размером, равным размеру заголовка. ; eax ^ │ ecx │ ; ───────>│──│<───────>│ ; [ebx] ; ; Иначе выделение свободной области ; ┌──┬──┬───────────┐ ┌──┬──┬─────────┐ Случай, когда выделение памяти возможно. ; │ │ │ Св.Эл.│ или │ │ │ Св.Эл.│ ; └──┴──┴───────────┘ └──┴──┴─────────┘ ; ^ eax │ ecx │ ^ eax │ ecx │ ; │<─────>│<───────>│ │<───>│<───────>│ ; [ebx] [ebx] mov [ebx].Lsiz1, eax ; Уменьшается размер элемента свободной памяти add ebx, eax ; ebx устанавливается на начало выделенного участка ; После выделения памяти ; ; ┌─┬─┬───────────────┐ ; │ │ │ Св.Эл.│ │ ; └─┴─┴───────────────┘ ; │ ^ │ ; │ eax │ ecx │ ; │<─────────>│<─────>│ ; [ebx] jmp OK ;──────────────────────────────────────────────────────────────────── nxt: mov edx, ebx ; В edx загружается указатель на данный элемент mov ebx, [ebx].Lnext ; в EBX переход на следующий свободный элемент cmp ebx, nill ; Если следующая ссылка пустая, то выход с ошибкой je exer jmp rpt ; Проверка следующей области ; Регистры ebx и edx в процессе поиска подходящего по размеру свободного элемента ; ; ┌────────────────────┐ ; │ v ; ┌─┬─┬──────────┐ ┌─┬─┬────────┐ ; │ │ │ Св. Эл. │ │ │ │ Св. Эл.│ ; └─┴─┴──────────┘ └─┴─┴────────┘ ; ^ ^ ; │ │ ; [edx] [ebx] ; endm ;═══════════════════════════════════════════════════════════════════════════════ ; Следующий макрос освобождает блок памяти объёмом ECX с начальным адресом, указанным в EBX. ; Важно! Возвращать в кучу память объёмом менее 8 байт опасно, т. к. при этом велика ; вероятность разрушения списковой структуры. ; Вход: eсx-высвобождаемый объём ОЗУ ; ds-сегмент кучи ; ebx-адрес, высвобождаемой озу ; LBMN -- метка или индексный регистр (любой, кроме edx), в котором находится ; ссылка на головной элемент. ; Выход: Нет ; Используется: eax, ebx, ecx, edx, edi, eflags ; ; Данный макрос очень объёмен и потому должен быть оформлен как процедура LFreeMem, ; например, так ; ; LFreemem proc ; ; Вход: ECX -- размер возвращаемого блока ОЗУ ; ; EBX -- указатель на начало возвращаемого блока ОЗУ ; LFreem LBMN ; ret ; endp LFreeM macro LBMN:Req local next_free, retmem, NoContact1, YesContact1, NoContact2, ex lea edx, [LBMN].Lfirstfree ; Загружает адрес ссылки на первый свободный элемент mov edi, [edx] ; Загружает указатель на первый свободный элемент ; EDX хранит адрес предыдущего свободного элемента ; В начале в него загружается указатель на ; поле главного элемента, содержащее список свободных элементов ; ┌─────────────────────┐ ┌───────── ecx ; │ v │ │<─────>│ ; ┌──┬──┬────────┐ ┌──┬──┬────────┐ ┌───────┐ ; │ │ │ Св.Эл. │ │ │ │ Св.Эл. │ │ │ ; └──┴──┴────────┘ └──┴──┴────────┘ └───────┘ ; ^ ^ ^ ; │ │ │ ; [edx] [edi] [ebx] ;─────────────────────────────────────────────────────────────────────────── ; Найдём такую пару последовательно расположенных свободных элементов, что ; высвобождаемый блок, указанный в EBX, располагался между ними, или, если ; такой пары нет, дойдём до последнего элемента списка свободной памяти. ; ┌─────────────────────────┐ ; │ v ; ┌──┬─┬─────┐ ┌───────┐ ┌──┬──┬─────┐ ┌────┬──┬──────┐ ┌─────────┐ ; │ │ │Св.Эл│ │ │ │ │ │Св.Эл│ или │nill│ │Св.Эл.│ │ │ ; └──┴─┴─────┘ └───────┘ └──┴──┴─────┘ └────┴──┴──────┘ └─────────┘ ; ^ ^ ^ ^ ^ ; │ │ │ │ │ ; [edx] [ebx] [edi] [edx] [ebx] edi=nill next_free: cmp edi, ebx ; сравнение адреса высвобождаемой ОЗУ с адресом свободного элемента ja retmem ; если адрес свободного элемента больше, чем адрес высвобождаемой ; области, то выполняется переход к высвобождению mov edx, edi ; в противном случае переход к следующему свободному элементу mov edi, [edi].Lnext ; Переход к следующему свободному элементу ; EDX при этом указывает на данный jmp next_free ; повторить проверку ;─────────────────────────────────────────────────────────────────────────── retmem: ; Проверим, соприкасается ли возвращаемый блок с предыдущим свободным элементом. ; Проверка: если предыдущий свободный элемент соприкасается с высвобождаемой областью, то ; происходит их объединение mov eax, [edx].Lsiz1 ; Складываем размер предыдущего блока с add eax, edx ; его базой (eax= база+размер предыдущего элемента) cmp eax, ebx jb NoContact1 ; ┌──────────────────────────────────┐ ; │ v До объединения ; ┌──┬──┬────────┐┌──────────────┐ ┌──┬──┬───────────────┐ ; │X │Y │Св.Эл. ││ │ │ │ │ Св. Эл. │ ; └──┴──┴────────┘└──────────────┘ └──┴──┴───────────────┘ ; ^ Y ^ ^ ; │<────────────> │ │ ; [edx] [ebx] [edi] ;────────────────────────────── ; если блоки соприкасаются, то объединяем их add ecx, [edx].Lsiz1 ; Складываем размеры предыдущего элемента свободной памяти ; и возвращаемого блока (вычисляем размер объединённой области) mov ebx, edx ; Устанавливает указатель возвращаемого блока jmp YesContact1 ; на начало объединённой области. ; ┌───────────────────────────────┐ ; │ v ; ┌──┬──┬─────────────────────┐ ┌──┬──┬───────────────┐ После объединения ; │X │Y │Св.Эл. │ │ │ │ Св. Эл. │ ecx=ecx+Y, ; └──┴──┴─────────────────────┘ └──┴──┴───────────────┘ на который указывает ebx ; ^ ^ ; │ │ ; [edx], [ebx] [edi] ; После выполнения команд ebx указывает на элемент свободной памяти ;─────────────────────────────────────────────────────────────────────────── NoContact1: ; установить в предыдущем указатель на высвобождаемый элемент ; Если объединения областей не произошло, то устанавливается ; указатель в предыдущем свободном элементе на высвобождаемый ; участок ОЗУ mov [edx].Lnext, ebx ; ; ┌───────────────────────────────────┐ Связи до выполнения команд ; │ v ebx -- указывает на элемент ; ┌─┬─┬───────┐ ┌─┬─┬───────┐ ┌─┬─┬───────┐ свободной ОЗУ ; │ │ │ │ │ │ │ │ │ │ │ │ eax -- размер свободной памяти ; └─┴─┴───────┘ └─┴─┴───────┘ └─┴─┴───────┘ В памяти элемента нового - ничего ; │ ^ ^ ; └──────────────────┘ │ ; ^ ^ │ ; │ │ │ Связи после выполнения команд ;[edx] [ebx] [edi] eax-размер блока памяти, ; на который указывает ebx YesContact1: cmp edi, nill je Ex mov eax, ECX ; Проверка: если последующий свободный элемент соприкасается ; с высвобождаемой областью, то происходит их объединение add eax, ebx ; Складываем размер высвобождаемого блока с его базой cmp eax, edi jb NoContact2 ; Если области не соприкасаются, то переход ;────────────────────────────── ; eax -- новый размер ; если блоки соприкасаются, то объединяем их add ecx, [edi].Lsiz1 ; вычисляет размер объединённой области ; eax -- новый размер mov edi, [edi].Lnext jmp ex ; До выполнения команд После выполнения команд ; ; ┌────────────┐ ┌───────────────────────────┐ ; │ v │ v ; ┌─┬─┬──────┐┌─┬─┬────┐ ┌─┬─┬───────┐ ┌─┬─────┬─────┬─┬─┬────┐ ┌─┬─┬───────┐ ; │?│?│ ││X│Y│ │ │ │ │ │ │X│Y+ecx│ │X│Y│ │ │ │ │ │ ; └─┴─┴──────┘└─┴─┴────┘ └─┴─┴───────┘ └─┴─────┴─────┴─┴─┴────┘ └─┴─┴───────┘ ; ^ ^ ^ ; │ ECX │ │ ; │<─────────>│ [ebx], [edi] ; [ebx] [edi] ; ; ┌──────────────────┐ Связи до выполнения команд ; │ v ; ┌─┬─┬───────┐ ┌─┬─┬───────┐ ┌─┬─┬───────┐ ; │ │ │ │ │X│Y│ │ │ │ │ │ Если не произошло ни ; └─┴─┴───────┘ └─┴─┴───────┘ └─┴─┴───────┘ одного объединения ; │ ^│ ^ ; └──────────────────┘└────────────────┘ ; ^ ^ ^ ; │ │ │ Связи после выполнения команд ;[edx] [ebx] [edi] X=edi, Y=ecx NoContact2: ex: mov [ebx].Lsiz1, ecx mov [ebx].Lnext, edi endm ;═════════════════════════════════════════════════════════════════════════════════ ; Макрос выделяет память под элемент типа SN ; Вход: ; SN -- тип списка, для которого выделяется память ; AddSize -- дополнительная память, используется, если элемент требует её наличия ; желательно указывать в ecx ; Выход: ebx -- указатель на элемент, если память была выделена, или переход по метке ; ExErr, если память не была выделена. ; Портит ecx (содержит размер элемента). LGetEl macro SN:Req, AddSize, ExErr:Req _LIsItDefTypeEl SN if L?&SN&?Siz EQ variable ; Помещает в ECX требуемый размер памяти errifb "Опущен параметр AddSize" movreg ecx, AddSize add ecx, size SN + L?&SN&?Hd elseifnb .err Элементы данного типа не допускают наличия дополнительной памяти else mov ecx, L?&SN&?Siz endif errifndef LGetMem "Макрос требует, чтобы была определена процедура LGetMem" call LGetMem ; Выделить ОЗУ (требуемый объём -- в ECX) jc ExErr add ebx, L?&SN&?Hd ; Переместить указатель на начало структуры SN if L?&SN&?Siz EQ variable ; Если размер структуры переменный, то записать if L?&SN&?dir EQ 1 ; размер в элемент mov [ebx].LSiz1-L?&SN&?Hd, ecx else mov [ebx].LSiz2-L?&SN&?Hd, ecx endif endif endm ;───────────────────────────────────────────────────────────────────────────────────────── ; Макрос выделяет память для блока типа TypeBlock ; Вход: ; BlockName -- Имя типа блока, может быть опущено. ; BlockSize -- требуемый размер блока. Указывается для блоков переменного размера или ; нетипизированных блоков. В последнем случае он ДОЛЖЕН БЫТЬ НЕ МЕНЕЕ 8 байт. ; Для корреции размера можно пользоваться макросом LCorrectSize. ; Выход: ebx -- указатель на элемент, если память была выделена, или переход по метке ; ExErr, если память не была выделена. ; Портит ecx (содержит размер элемента). LGetBlock macro BlockName, BlockSize, ExErr:Req ifnb _LIsItDefTypeBlk BlockName if L?&BlockName&?Siz EQ variable ; Помещает в ECX требуемый размер памяти errifb "Опущен параметр BlockSize" movreg ecx, BlockSize add ecx, L?&BlockName&?Hd elseifnb .err "Блок данного типа имеет фиксированный размер, и в его указании не нуждается" else mov ecx, L?&BlockName&?Siz endif errifndef LGetMem "Макрос требует, чтобы была определена процедура LGetMem" call LGetMem ; Выделить ОЗУ (требуемый объём -- в ECX) jc ExErr add ebx, L?&BlockName&?Hd ; Переместить указатель на начало структуры if L?&BlockName&?Siz EQ variable ; Если размер структуры переменный, то записать mov [ebx].LSiz0-L?&BlockName&?Hd, ecx ; размер в заголовок блока endif else movreg ecx, BlockSize ; Если блок без типа, то выделить запрошенный объём call LGetMem ; ОЗУ. jc ExErr endif endm ;───────────────────────────────────────────────────────────────────────────────────────── ; Макрос возвращает память, занятую элементом типа TypeEl в кучу ; Вход: ; TypeEl -- тип элемента ; ebx -- указатель на элемент, который нужно вернуть в кучу. ; Выход: Нет ; Портит: eax, ebx, ecx, edx, edi, eflags LFreeEl macro SN:Req _LIsItDefTypeEl SN sub ebx, L?&SN&?Hd if L?&SN&?Siz EQ variable ; Помещает в ECX размер памяти, занимаемый элементом if L?&SN&?dir EQ 1 mov ecx, [ebx].LSiz1 else mov ecx, [ebx].LSiz2 endif else mov ecx, L?&SN&?Siz endif errifndef LFreeMem "Макрос требует, чтобы была определена процедура LFreeMem" call LFreeMem endm ;───────────────────────────────────────────────────────────────────────────────────────── ; Макрос возвращает память, занятую элементом типа TypeBlock в кучу ; Вход: ; BlockName -- имя типа блока, если отсутствует, значит блок не типизированный и должен быть ; указан параметр BlockSize. ; BlockSize -- Размер блока памяти, который надо освободить, используется, ; только для нетипизированных блоков ; ebx -- указатель на элемент, который нужно вернуть в кучу. ; Выход: Нет ; Портит: eax, ebx, ecx, edx, edi, eflags LFreeBlock macro BlockName, BlockSize ifnb _LIsItDefTypeBlk BlockName errifnb "Для типизированных блоков нет нужды указывать их размер" sub ebx, L?&BlockName&?Hd if L?&BlockName&?Siz EQ variable ; Помещает в ECX размер памяти, занимаемый блоком mov ecx, [ebx].LSiz0 else mov ecx, L?&BlockName&?Siz endif errifndef LFreeMem "Макрос требует, чтобы была определена процедура LFreeMem" call LFreeMem elseifnb movreg ecx, BlockSize errifndef LFreeMem "Макрос требует, чтобы была определена процедура LFreeMem" call LFreeMem else .err "Не указан ни один параметр блока" endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Макрос устанавливает ссылки в элементе списка так, как если бы он был первым ; и единственным элементом в списке ; SN -- имя списка ; Reg -- регистр, указывающий на элемент ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. LMakeFirstEl macro SN:Req, Reg:= _LIsItDefTypeEl SN if L?&SN&?Crc EQ Yes ; Для циклического списка mov [Reg].LNext-L?&SN&?Hd, Reg ; В указатели на предыдущий и последующий if L?&SN&?dir EQ 2 ; элемент записываются ссылки на mov [Reg].Lpred-L?&SN&?Hd, Reg ; сам элемент endif elseif L?&SN&?Crc EQ No ; Для линейного списка mov [Reg].LNext-L?&SN&?Hd, nill ; Делает ссылки на предыдущий if L?&SN&?dir EQ 2 ; и последующий элементы пустыми mov [Reg].Lpred-L?&SN&?Hd, nill ; endif else .err Элемент, вероятно, не определён или определён не верно endif endm LMakeSubFirstEl macro SN:Req, Fld:Req, Reg:=, IReg:Req _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ Yes ; Для циклического списка mov [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, Reg ; В указатели на предыдущий и последующий if L?&SN&?&Fld&?dir EQ 2 ; элемент записываются ссылки на mov [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, Reg ; сам элемент endif elseif L?&SN&?&Fld&?Crc EQ No ; Для линейного списка mov [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, nill ; Делает ссылки на предыдущий if L?&SN&?&Fld&?dir EQ 2 ; и последующий элементы пустыми mov [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, nill endif else .err Элемент, вероятно, не определён или определён не верно endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Макрос устанавливает ссылки в элементе списка так, как если бы он был последним ; элементом в списке (т. е. в случае нециклического списка устанавливает ссылку на ; следующий элемент пустой) ; SN -- имя списка ; Reg -- регистр, указывающий на элемент LMakeLast macro SN:Req, Reg:= _LIsItDefTypeEl SN if L?&SN&?Crc EQ Yes ; Для циклического списка ; Если список циклический -- ни чего не делать elseif L?&SN&?Crc EQ No ; Для линейного списка mov [Reg].LNext-L?&SN&?Hd, nill ; Делает ссылки на предыдущий else .err Элемент, вероятно, не определён или определён не верно endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Проверяет, является ли элемент последним в списке или подсписке. В линейном списке ; элемент последний, если ссылка на последующий элемент пустая, В кольцевом списке ; элемент последний, если он указывает сам на себя. ; Вход: ; SN -- имя типа ; Reg -- регистр, указывающий на элемент, по умолчанию [ebx] ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. ; Выход: Flags -- Если элемент последний, то флаги установлены так, что по команде ; "je xxx", выданной после макроса, будет выполнен переход по метке "xxx". LChckLast macro SN:REQ, Reg:= _LIsItDefTypeEl SN if L?&SN&?Crc EQ Yes cmp Reg, [Reg].LNext-L?&SN&?Hd else cmp [Reg].LNext-L?&SN&?Hd, NILL endif endm LChckSubLast macro SN:REQ, Fld:req, Reg:=, IReg:=<0> _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ Yes cmp Reg, [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext else cmp [Reg].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, NILL endif endm ;══════════════════════════════════════════════════════════════════════════════════════ ; Проверяет, является ли элемент первым в списке (подсписке). Используется только для линейных ; двунаправленных списков. Если ссылка на предыдущий элемент пустая, то элемент -- первый. ; Вход: ; SN -- имя типа ; Reg -- регистр, указывающий на элемент, по умолчанию [ebx] ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. ; Выход: Flags -- Если элемент первый, то флаги установлены так, что по команде ; "je xxx", выданной после макроса, будет выполнен переход по метке "xxx". LChckFirst macro SN:REQ, Reg:= _LIsItDefTypeEl SN if L?&SN&?Crc EQ No if L?&SN&?dir EQ 2 cmp [Reg].LPred, NILL else .err "Список должен быть линейным двунаправленным" endif else %out "Список должен быть линейным двунаправленным" endif endm LChckSubFirst macro SN:REQ, Fld:req, Reg:=, IReg:=<0> _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ No if L?&SN&?&Fld&?Dir EQ 2 cmp [Reg].LPred.&Fld&.[IReg*L?&SN&?&Fld&?dir], NILL else .err "Список должен быть линейным двунаправленным" endif else .err "Список должен быть линейным двунаправленным" endif endm ;═════════════════════════════════════════════════════════════ ; Проверяет размер затребованной ОЗУ и, если надо, корректирует его. Если размер ; запрашиваемой ОЗУ меньше 8 байт, то он приравнивается 8 байтам. ; Вход ReqSize -- регистр или память, содержащий затребованный объём ОЗУ ; Выход ReqSize -- Содержит скорректированный объём ОЗУ ; Flags -- флаги. Если выдать команду `jb xxx', то, если коррекция выполнялась, ; будет выполнен переход по метке 'xxx', а если 'jae xxx' -- то если не выполнялась. ; JNoCorrect -- переход по метке, если коррекция не выполноялась, параметр не обязателен. LCorrectSize macro ReqSize:req, JNoCorrect local Skip cmp ReqSize, 8 ifb jae Skip else jae JNoCorrect endif mov ReqSize, 8 Skip: endm ;═════════════════════════════════════════════════════════════ ; Выполняет вставку первого элемента в линейный список или подсписок. ; Вход: ; SN -- тип списка ; Reg1 -- Элемент, который должен быть вставлен, по умолчанию [ebx] ; Reg2 -- Первый элемент списка, если Reg2=nill, то в [reg2] ни чего не заносится ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. ; ; ┌────────┐┌────────┐┌────────┐┌── ; │ v│ v│ v│ ; ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ; │ │ а │ │ │ б │ │ │ │ │ │ │ │ │ │ ; └─┴────┘ └─┴────┘ └─┴────┘ └─┴────┘ └─┴────┘ ; ^ ^ ; │ │ ; Reg1 Reg2 LIncFirst macro SN:req, Reg1:=, Reg2:req local Skip _LIsItDefTypeEl SN if L?&SN&?Crc EQ No mov [Reg1].LNext-L?&SN&?Hd, Reg2 ; "а.сл" <── ссылка на "б" if L?&SN&?dir EQ 2 ; Если список двунаправленный mov [Reg1].LPred-L?&SN&?Hd, Nill ; "a.пр"<-- Nill cmp Reg2, Nill ; Если Reg2 -- не пустая ссылка, то je Skip mov [Reg2].LPred-L?&SN&?Hd, Reg1 ; "б.пр" <-- ссылка на "а" Skip: endif else .err "Данный макрос может быть использован только для линейных списков" endif endm LIncSubFirst macro SN:req, Fld:req, Reg1:=, Reg2:req, IReg:=<0> local Skip _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ No mov [Reg1].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, Reg2 ; "а.сл" <── ссылка на "б" if L?&SN&?dir EQ 2 ; Если список двунаправленный mov [Reg1].&Fld&.[IReg*L?&SN&?&Fld&?dir].LPred, Nill cmp Reg2, Nill ; "a.пр"<-- Nill je Skip ; Если Reg2 -- не пустая ссылка, то mov [Reg2].&Fld&.[IReg*L?&SN&?&Fld&?dir].LPred, Reg1 Skip: ; "б.пр" <-- ссылка на "а" endif else .err "Данный макрос может быть использован только для линейных подсписков" endif endm ;═════════════════════════════════════════════════════════════ ; Выполняет вставку последнего элемента в линейный список или подсписок. ; Вход: ; SN -- тип списка ; Reg1 -- Элемент, который должен быть вставлен, по умолчанию [ebx] ; Reg2 -- Последний элемент списка ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. ; ; ┌────────┐┌────────┐┌────────┐ ; │ v│ v│ v ; ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ┌─┬────┐ ; │ │ │ │ │ │ │ │ │ │ │ а │ │ │ б │ ; └─┴────┘ └─┴────┘ └─┴────┘ └─┴────┘ └─┴────┘ ; ^ ^ ; │ │ ; Reg2 Reg1 LIncLast macro SN:req, Reg1:=, Reg2:req local Skip _LIsItDefTypeEl SN if L?&SN&?Crc EQ No mov [Reg1].LNext-L?&SN&?Hd, Nill ; "б.сл" <── ссылка на Nill cmp Reg2, Nill je Skip mov [Reg2].LNext-L?&SN&?Hd, Reg1 ; "а.сл" <── ссылка на "б" Skip: if L?&SN&?dir EQ 2 ; Если список двунаправленный mov [Reg1].LPred-L?&SN&?Hd, Reg2 ; "б.пр" <── ссылка на "а" endif else .err "Данный макрос может быть использован только для линейных списков" endif endm LIncSubLast macro SN:req, Fld:req, Reg1:=, Reg2:req, IReg:=<0> local Skip _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ No mov [Reg1].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, Nill ; "б.пр"<-- Nill cmp Reg2, Nill je Skip ; Если Reg2 -- не пустая ссылка, то mov [Reg2].&Fld&.[IReg*L?&SN&?&Fld&?dir].LNext, Reg1 ; "а.сл" <── ссылка на "б" Skip: if L?&SN&?dir EQ 2 ; Если список двунаправленный mov [Reg1].&Fld&.[IReg*L?&SN&?&Fld&?dir].LPred, Reg2 ; "б.сл" <-- ссылка на "а" endif else .err "Данный макрос может быть использован только для линейных подсписков" endif endm ;═════════════════════════════════════════════════════════════ ; Выполняет исключение первого элемента из линейного списка. ; Вход: Reg1 -- Указатель на исключаемый элемент. Обязателен. ; Reg2 -- Регистр, в который будет занесён указатель на второй элемент. Обязателен. ; Если второй элемент не существует, то Reg2=Nill ; SN -- Тип списка. Обязателен. ; Skp -- Метка, по которой может быть осуществлён переход, если второй элемент не ; существует. Необязательный параметр. ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. ; Выход: Reg2 указатель на следующий элемент, если он существует. Если элемент отсутствует, то ; Reg2=Nill, если при этом указана метка Skp, то о ней будет выполнен переход. LExclFirst macro SN:Req, Reg1:Req, Reg2:Req, Skp local Skip _LIsItDefTypeEl SN if L?&SN&?Crc EQ No mov Reg2, [Reg1].Lnext-L?&SN&?Hd ifnb cmp Reg2, Nill je Skp endif if L?&SN&?dir EQ 2 cmp Reg2, Nill ifb je Skip else je Skp endif mov [Reg2].LPred-L?&SN&?Hd, Nill endif else .err "Данный макрос может быть использован только для линейных списков" endif Skip: endm LExclSubFirst macro SN:Req, Fld:req, Reg1:Req, Reg2:Req, Skp, IReg:=<0> local Skip _LIsItDefTypeSubEl SN, Fld if L?&SN&?&Fld&?Crc EQ No mov Reg2, [Reg1].&Fld&.[IReg*L?&SN&?&Fld&?dir].Lnext ifnb cmp Reg2, Nill je Skp endif if L?&SN&?&Fld&?Dir EQ 2 cmp Reg2, Nill ifb je Skip else je Skp endif mov [Reg2].&Fld&.[IReg*L?&SN&?&Fld&?dir].LPred, Nill Skip: endif else .err "Данный макрос может быть использован только для линейных подсписков" endif endm ;══════════════════════════════════════════════════════════════════════════════════ ; Следующие макросы описывают загрузку указателей на следующий элемент списка типа А ; (LLoadNext) в регистр, а также на элемент подсписка типа Б или на следующий элемент ; многомерного списка типа В (LLoadSubNext). ; Вход: ; SN -- имя структуры (тип элемента) ; LReg -- указатель на начало текущего элемента ; Reg -- Регистр, в который загружается указатель на следующий элемент ; NoEl -- метка, на которую переходит макрос в случае, если следующий элемент ; не существует. Нужен только для линейных списков, не обязателен. ; Выход: [LReg] -- новый элемент, или переход по метке, exer если следующий элемент ; отсутствует. ; Для макроса LGetSubNext должны дополнительно указываться следующие параметры ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. LLoadnext macro SN:Req, LReg:Req, Reg:Req, NoEl ; Переход к следующему элементу списка типа А local Reg? _LIsItDefTypeEl SN IsItReg?d LReg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" if L?&SN&?Crc EQ No ; Если список линейный, то проверить ifnb cmp [LReg].Lnext-L?&SN&?Hd, nill ; является ли указатель на следующий je NoEl ; элемент пустым, и, если является, то выход с ошибкой endif endif mov Reg, [Lreg].Lnext-L?&SN&?Hd ; Переход к следующему элементу endm LLoadSubNext macro SN:Req, Fld:Req, LReg:Req, Reg:Req, NoEl, IReg ; Переход к следующему local Reg? ;элементу подсписка (для типов Б и В) _LIsItDefTypeSubEl SN, Fld IsItReg?d Reg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" if L?&SN&?&Fld&?ItsTable EQ No if L?&SN&?&Fld&?Crc EQ No ifnb cmp dword ptr [LReg].&Fld&.Lnext, nill ; Указатель пустой? je NoEl ; Да - ошибка endif endif mov Reg, [LReg].&Fld&.Lnext ; Переход в подсписок else errifb "Для таблиц подсписков должен обязательно указываться параметр IReg" if L?&SN&?&Fld&?Crc EQ No ifnb cmp dword ptr [LReg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lnext, nill ; Указатель пустой? je NoEl ; Да - ошибка endif endif mov Reg, [LReg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lnext ; Переход в подсписок endif endm ;════════════════════════════════════════════════════════════════════════───────── ; Следующие макросы осуществляют загрузку указателя на предыдущий элемент в регистр, могут ; применяться только к двунаправленным спискам (LGetPred) и к двунаправленным многомерным ; спискам типа В (LGetSubPred). ; SN - имя структуры (тип элементов) ; LReg -- указатель на начало элемента ; Reg -- Регистр, в который загружается указатель на предыдущий элемент ; NoEl -- метка, на которую переходит макрос в случае, если следующий элемент ; не существует. ; Выход: Reg -- новый элемент, или переход по метке exer, если элемент отсутствует ; Для макроса LGetSubPred должны дополнительно указываться следующие параметры ; Fld -- указывает на поле в SN являющееся ссылкой (таблицей ссылок) в элементе ; и используется при входе в подсписок или при перемещении внутри многомерных ; списков. Обязателен. ; IReg -- индексный регистр, содержащий номер интересующей ссылки в таблице ; ссылок Fld. Необходим и используется только в том случае, когда Fld ; является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ; ссылки в Fld. LLoadPred macro SN:Req, LReg:Req, Reg:Req, NoEl ; Переход к предыдущему элементу local Reg? ; списка (только для двунаправленного) _LIsItDefTypeEl SN IsItReg? LReg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" errif L?&SN&?dir EQ 1 "Невозможна загрузка указателя на предыдущий элемент, т. к. список однонаправленный" if L?&SN&?Crc EQ No ; Если список линейный, то проверить ifnb cmp [LReg].Lpred-L?&SN&?Hd, nill ; является ли указатель на предыдущий je NoEl ; элемент пустым и если является, то сообщение об ошибке endif endif mov Reg, [LReg].Lpred-L?&SN&?Hd ; Переход к предыдущему элементу endm LLoadSubPred macro SN:Req, Fld:Req, LReg:Req, Reg:Req, NoEl, IReg ; Переход к предыдущему local Reg? ; элементу подсписка (только для ; типа В, причём двунаправленного) _LIsItDefTypeSubEl SN, Fld IsItReg?d LReg, Reg? errif reg? EQ No "Параметр Reg должен быть регистром" errif L?&SN&?&Fld&?dir EQ 1 "Невозможна загрузка указателя на предыдущий элемент, т. к. список однонаправленный" if L?&SN&?&Fld&?ItsTable EQ No errifb "Для таблиц подсписков должен обязательно указываться параметр IReg" if L?&SN&?&Fld&?Crc EQ No ; Если список линейный, то проверить ifnb cmp [LReg].&Fld&.Lpred, nill ; является ли указатель на предыдущий je NoEl ; элемент пустым и если является, то сообщение об ошибке endif endif mov Reg, [LReg].&Fld&.Lpred ; Переход к предыдущему элементу else if L?&SN&?&Fld&?Crc EQ No ; Если список линейный, то проверить ifnb cmp [LReg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred, nill ; является ли указатель на предыдущий je NoEl ; элемент пустым и если является, то сообщение об ошибке endif endif mov Reg, [LReg].&Fld&.[IReg*4*L?&SN&?&Fld&?dir].Lpred ; Переход к предыдущему элементу endif endm ;┌───────────────────────────────────────────────────────────────────────────────┐ ;│ Некоторые нюансы, касающиеся работы TASM 5.0 │ ;│ (Приведены по тому, что на них опирается работа некоторых макросов) │ ;│ │ ;│ - Для команды "ifidni , <0>" определения │ ;│ │ ;│... macro variable │ ;│ и │ ;│... macro 0 │ ;│ не одно и то же (хотя variable и определена как "variable EQ 0"). │ ;│ │ ;│ В конструкциях вида │ ;│ xxx=$ │ ;│ yyy dd xxx │ ;│ существенно, как определён сегмент. Если в определении сегмента стоит │ ;│ 'use16', то младшие 16 бит 'yyy' будут содержать правильное значение, а │ ;│ старшие 16 бит -- не правильное. Поэтому в таких конструкциях сегмент должен │ ;│ в своём объявлении содержать 'use32'. │ ;│ Это существенно при использовании макросов 'LDeclare...'. │ ;│ │ ;│ │ ;└───────────────────────────────────────────────────────────────────────────────┘ ; Конец файла HEAP.mac