Здесь приводятся описание макросов для работы с кучей heap.mac.
Данный файл содержит описания макросов для работы со списковыми структурами данных (выделение из кучи и возврат в неё памяти, переход к предыдущему и последующему элементам и др.). Списки могут быть кольцевыми или линейными, одно- или двунаправленными, состоящими из элементов фиксированного или переменного размера. Элементы списков помимо прочего могут содержать указатели на динамически выделяемые блоки памяти и подсписки, подобно головной структуре.
Уборка мусора в куче и дефрагментация свободного пространства в ней не производится ввиду того, что ссылки могут размещаться не только в заголовке, но и в любом месте структуры, описывающей элемент списка.
Перед началом работы создаётся «головной элемент», содержащий информацию о размере области, отводимой под кучу, указатели на некоторые (обычно первые) элементы списков и др. К головному элементу может добавляться дополнительная область памяти содержащая какую-либо специфическую информацию, которую неудобно размещать в структуре.
Головной элемент — область статических переменных, предназначенная для хранения указателя на список свободной памяти, размера памяти, выделенной под кучу, указателей на первые элементы списков и каких-нибудь иных статических данных нужных программисту. Ниже приведён заголовок головного элемента, с которого начинается любой головной элемент.
Неструктурированная память может быть использована в тех случах, когда по тем или иным причинам размещение данных в основной структуре головного элемента SN нецелесообразно.
Прежде, чем использовать кучу в программе, должен быть вызван макрос LInitHeap. Примеры вызова макроса
LInitHeap MainStruc, ebx, 10240 | ; Установить кучу размером 10 кБ, в месте, на кторое |
; указывает ebx, с основной структурой MainStruc |
LInitHeap MainStruc, MainAddr, ebx, , 4 | ; Установить по метке MainAddr кучу с головной структурой |
; MainStruc, в которой первые четыре указателя устанав- | |
; ливаются пустыми. Размер кучи указан в ebx |
Следующий макрос формирует головной элемент в памяти, устанавливает список свободных элементов, и устанавливает пустые ссылки в первых Fnill элементах структуры SN.
При использовании Fnill ES и DS должны указывать на один и тот же сегмент.
LInitHeap macro SN, LBMN:REQ, SHeap:REQ, SizExt:=<0>, Fnill, SFree:=<0>
Запись в область данных головной структуры может производиться как
mov [указатель_на_головной_элемент].LUsD.поле_структуры_пользователя, данные
указатель_на_головной_элемент — метка или ссылка в базовом регистре.
Элемент списка в общем случае выглядит так
Здесь SN — некоторая структура данных, её имя используется как имя типа; reg — один из РОНов. «Размер данного элемента списка» включает размер заголовка, размер структуры элемента и размер памяти, выделенной после структуры.
Кроме списков могут также выделяться блоки динамической памяти. Их вид следующий
Запись в область данных элемента списка или блока может производиться как
mov [Reg].поле_структуры, данные
Обращение к пользовательской части заголовка элемента списка или блока:
mov [Reg-L?&SN&?ExtHd].поле_структуры, данные
Перед использованием операций над элементами списков и подсписков (переходы от одного элемента к другому, выделение и возврат памяти и т. п.), должны быть определены типы списков и подсписков с помощью макросов LDefType и LDefSubType.
Примеры вызова этих макросов
LDefType StrucName1, 1,, Circle | ; Определяет однонаправленный циклический список со структурой |
; фиксированного размера StrucName1 |
LDefType StrucName2, 2, variable | ; Определяет двунаправленный линейный список со структурой StrucName2. |
; Отдельные элементы этого списка могут иметь различный размер. |
LDefSubType StrucName2, FldPoint, 1,, ItsTable | ; Указывает, что поле FldPoint структуры |
; StrucName2 — таблица ссылок на элементы (тип Б или В), | |
; образующие однонаправленные линейные списки. |
Макросы, описанные в данном файле, предполагают, что каждый список состоит из однотипных элементов, имеющих заголовок, структуру данных SN, и (необязательно) дополнительную область памяти, размер которой может меняться от элемента к элементу. Тип описывается с помощью макроса LDefType. SN является и именем структуры данных, используемых программистом, и именем типа списка. Если требуется создать списки с разным способом доступа к отдельным элементам (например, циклический однонаправленный и линейный двунаправленный) с одинаковой структурой данных, то следует описать её в двух разных структурах данных SN. При описании можно использовать директиву equ:
SN1 | struc | |
. | ||
. | ||
ends | ||
SN2 | equ | SN1 |
Далее, можно описать два разных типа SN1 и SN2. В памяти может создаваться несколько списков с одинаковой структурой данных SN и одинаковым методом доступа. Заголовок элемента формируется на основе значений числа направлений в списке dir и величины дополнительной неструктурированной памяти Addmem.
Вход:
LDefType macro SN:Req, Direct:Req, Addmem:=<0>, Circle, ExtHd:=<0>
Следующие макросы предназначены для формировании в сегменте данных готовых списков.
LDeclareBegin SN:req, ListN:req
LDeclare ListN1
LDeclareEnd
Вход:
LDeclareBegin macro SN:req, ListN:req
LDeclare macro ListN1
LDeclareEnd macro
В ряде случаев целесообразно создавать список ещё на этапе трансляции программы. Например, если разрабатывается интерпретатор, то на момент начала работы ему уже должен быть известен определённый набор имён функций, который по мере обработки текста программы может постепенно пополняться новыми терминами. Формирование списков на этапе трансляции обеспечивается макросами 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 |
LGetNext ebx, SN |
mov ebx, offset L2 |
LGetNext ebx, SN |
дадут правильный результат.
Ниже приведён макрос выделения памяти заданного размера.
Важно! В принципе макрос позволяет выделить память объёмом в один байт, но возвращать в кучу память объёмом менее восьми (8) байт опасно, т. к. при этом велика вероятность (около 100 %) разрушения списковой структуры.
Вход:
Выход: 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
Данный макрос очень объёмен и потому должен быть оформлен как процедура LFreemem, например, так
LFreemem proc | ; Вход: ECX — размер возвращаемого блока ОЗУ |
; EBX — указатель на начало возвращаемого блока ОЗУ | |
LFreem LBMN | |
ret | |
endp |
LFreem macro LBMN:Req
Макрос выделяет память под элемент типа SN Вход: SN — тип списка, для которого выделяется память AddSize — дополнительная память, используется, если элемент требует её наличия желательно указывать в ecx Выход: ebx — указатель на элемент, если память была выделена, или переход по метке ExErr, если память не была выделена. Портит ecx (содержит размер элемента).LGetEl macro SN:Req, AddSize, ExErr:Req
Макрос выделяет память для блока типа TypeBlock Вход: BlockName — Имя типа блока, может быть опущено. BlockSize — требуемый размер блока. Указывается для блоков переменного размера или нетипизированных блоков. В последнем случае он ДОЛЖЕН БЫТЬ НЕ МЕНЕЕ 8 байт. Для корреции размера можно пользоваться макросом LCorrectSize. Выход: ebx — указатель на элемент, если память была выделена, или переход по метке ExErr, если память не была выделена. Портит ecx (содержит размер элемента).LGetBlock macro BlockName, BlockSize, ExErr:Req
Макрос возвращает память, занятую элементом типа TypeEl в кучу Вход: TypeEl — тип элемента ebx — указатель на элемент, который нужно вернуть в кучу. Выход: Нет Портит: ebx, ecx.LFreeEl macro SN:Req
Макрос возвращает память, занятую элементом типа TypeBlock в кучу Вход: BlockName — имя типа блока, если отсутствует, значит блок не типизированный и должен быть указан параметр BlockSize. BlockSize — Размер блока памяти, который надо освободить, используется, только для нетипизированных блоков ebx — указатель на элемент, который нужно вернуть в кучу. Выход: Нет Портит: ebx, ecx.LFreeBlock macro BlockName, BlockSize
Память выделяется под элемент списка (или блок динамической памяти) по вызову макроса 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 — размер дополнительной |
; ОЗУ, выделяемой после структуры списка |
Следующие ниже обозначения являются общими для всех приводимых ниже макросов
Размер блока с учётом заголовка (если есть) должен быть не меньше 8 байт! Иначе при его возврате в кучу может быть нарушена структура списков. Блоки могут быть типизированными и нетипизированными. Для типизированных блоков фиксированного размера, размер увеличивается до 8 байт автоматически, для нетипизированных блоков и блоков переменного размера, он должен корректироваться программистом, например, с помощью макроса LCorrectSize. Типизированные блоки, в отличие от нетипизированных, определяются с помощью макроса LDefBlockType, и к типизированным блокам может быть применён макрос LDefSubType, а к нетипизированным — нет.
На основе элментов списков и блоков выделенной памяти могут организовываться древовидные структуры и многомерные списки. Возможные варианты размещения ссылок на подсписки в элементе приведены ниже.
Здесь P— указатель на элемент подсписка; PN— Указатель на следующий элемент подсписка, PP— указатель на предыду- щий элемент списка; Fld — поле, являющееся ссылкой или массивом ссылок; IReg— один из РОНов, содержащий номер ин- тересующей ссылки в массиве ссылок, на начало которого указывает Fld. Размер одной ссылки 4 байта. Если использует- ся двунаправленный список, то все ссылки сдвоенные (т. е. размер одного вхождения — 8 байт), указывают на сле- дующий и на предыдущий элементы. Перед таблицей ссылок может размещаться необязательное однобайтовое поле, содержа- щее количество ссылок в таблице (при этом количество ссылок (одно- или двунаправленных) должно лежать в пределах от 1 до 256 иначе они не поместятся в поле). Поле LapTabl должно быть описано в структуре SN в байте, пред- шествующем началу таблицы ссылок. Использовать поле имеет смысл только при динамическом определении количества ссы- лок. При статическом определении можно использовать более 256 ссылок. Проверка, выходит ли индекс за размер таблицы осуществляется макросом LChckInd, который должен указываться перед макросами, обращающимися к таблицам ссылок.
Пример:
LDefType SN1, 1 | |
LDefSubType SN1, Fld1, 2, No, ItsTable, variable | |
. | |
. | |
. | |
LChckInd SN1, Fld1, ebx, ExErr_OutOfRange, edx, dl | ; Проверка границы |
LGetSubNext SN1, Fld1, ebx, ExErr_NoSubItem, edx | ; Переход к вхождению |
Данный макрос определяет тип ссылок на элементы подсписков для типов подсписков Б и В.
Вход:
LDefSubType macro SN:Req, Fld:Req, Direct:Req, Circle, ItsTable, LapTablS
Макрос предназначен для определения типа блока, чтобы можно было использовать ссылки на другие подсписки (при совместном использовании с LDefSubType) и для автоматизации выделения памяти
LDefBlockType macro BlockName:REQ, BlockSize:=<variable>, ExtHd:=<0>
Переходы от одного элемента к другому осуществляются с помощью макросов 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 |
Следующие макросы описывают переходы к следующему элементу списка типа А (LGetNext), а также к входу в подсписок типа Б и к переходу к следующему элементу многомерного списка типа В (LGetSubNext).
Для макроса LGetSubNext должны дополнительно указываться следующие параметры
IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ссылки в Fld (числом).
LGetNext macro SN:Req, Reg:=<ebx>, exer:Req ; Переход к следующему элементу списка типа А
LGetSubNext macro SN:Req, Fld:Req, Reg:=<ebx>, exer:Req, IReg ; Переход к следующему
Следующие макросы осуществляют переход к предыдущему элементу, могут применяться только к двунаправленным спискам (LGetPred) и к двунаправленным многомерным спискам типа В (LGetSubPred).
Для макроса LGetSubPred должны дополнительно указываться следующие параметры
IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ссылки в Fld (числом).LGetPred macro SN:Req, Reg:=<ebx>, exer ; Переход к предыдущему элементу
LGetSubPred macro SN:Req, Fld:Req, Reg:=<ebx>, exer, IReg ; Переход к предыдущему элементу подсписка
Следующие макросы описывают загрузку указателей на следующий элемент списка типа А (LLoadNext) в регистр, а также на элемент подсписка типа Б или на следующий элемент многомерного списка типа В (LLoadSubNext).
Вход:
Выход:
Для макроса LGetSubNext должны дополнительно указываться следующие параметры
IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также сме- щением ссылки в Fld.LLoadnext macro SN:Req, LReg:Req, Reg:Req, NoEl ; Переход к следующему элементу списка типа А
LLoadSubNext macro SN:Req, Fld:Req, LReg:Req, Reg:Req, NoEl, IReg ; Переход к следующему
Следующие макросы осуществляют загрузку указателя на предыдущий элемент в регистр, могут применяться только к двунаправленным спискам (LGetPred) и к двунаправленным многомерным спискам типа В (LGetSubPred).
Для макроса LGetSubPred должны дополнительно указываться следующие параметры
LLoadPred macro SN:Req, LReg:Req, Reg:Req, NoEl ; Переход к предыдущему элементу
LLoadSubPred macro SN:Req, Fld:Req, LReg:Req, Reg:Req, NoEl, IReg ; Переход к предыдущему
Включение и исключение элементов из списка выполнют, соответственно, макросы LIncEl и LExcEl.
LIncEl StrucName1 | ; StrucName1 — имя типа элемент, ebx — вставляемый элемент |
; ecx — элемент списка, после которого нужно вставить элемент ebx |
Включение и исключение элементов из многомерных списков (тип В) выполнют, соответственно, макросы LIncSubEl и LExcSubEl
LExcSubEl StrucName2, FldPoint, edi | ; StrucName2 — имя типа элемент |
; ebx — исключаемый элемент | |
; edx — элемент списка, после которого нужно исключить элемент ebx | |
; FldPoint — Имя поля, которое является массивом ссылок | |
; edi — регистр, содержащий индекс интересующей ссылки в массиве FldPoint. |
В списке (подсписке) до вставки должно присутствовать не меньше одного элемента.
Портит регистр EAX
Для макроса LIncSubEl должны дополнительно указываться следующие параметры
IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также смещением ссылки в Fld (числом).Данный макрос не предназначен для работы с обычными подсписками (тип Б). Подсписки могут быть только многомерными (тип В). Портит eax
LIncEl macro SN:Req ; Вставка элемента в список
LIncSubEl macro SN:Req, Fld:Req, IReg ; Вставка элемента в подсписок
Макросы вставки элементов в списки (LExcEl) и многомерные подсписки типа В (LExcSubEl) В списке (подсписке) до вставки должно присутствовать не меньше 1 элемента.Если список — циклический, и элемент в нём — последний, то элемент исключён не будет. Портится EAX, при работе с двунаправленными списками EDI в списке должно быть минимум два элемента. Предыдущий элемент обязательно должен существовать, т. к. проверка его существования макросом не производится.
Для макроса LExcSubEl должны дополнительно указываться следующие параметры.
LExcEl macro SN:Req ; Исключить элемент из списка
LExcSubEl macro SN:Req, Fld:Req, IReg ; Исключить элемент из подсписка
Выполняет вставку первого элемента в линейный список или подсписок.
Вход:
LIncFirst macro SN:req, Reg1:=<ebx>, Reg2:req
LIncSubFirst macro SN:req, Fld:req, Reg1:=<ebx>, Reg2:req, IReg:=<0>
Выполняет вставку последнего элемента в линейный список или подсписок.
Вход:
LIncLast macro SN:req, Reg1:=<ebx>, Reg2:req
LIncSubLast macro SN:req, Fld:req, Reg1:=<ebx>, Reg2:req, IReg:=<0>
Выполняет исключение первого элемента из линейного списка.
Вход: eg1 — Указатель на исключаемый элемент. Обязателен. Reg2 — Регистр, в который будет занесён указатель на второй элемент. Обязателен. Если второй элемент не существует, то Reg2=Nill Skp — Метка, по которой может быть осуществлён переход, если второй элемент не существует. Необязательный параметр. IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также сме- щением ссылки в Fld. Выход: Reg2 указатель на следующий элемент, если он существует. Если элемент отсутствует, то Reg2=Nill, если при этом указана метка Skp, то о ней будет выполнен переход.LExclFirst macro SN:Req, Reg1:Req, Reg2:Req, Skp
LExclSubFirst macro SN:Req, Fld:req, Reg1:Req, Reg2:Req, Skp, IReg:=<0>
Если поле указывает на подсписок, то ссылка в него включается с помощью команды
mov [reg_1].имя_поля, reg_2где reg_2 — указывает на элемент подсписка, который надо включить.
Для контроля попадания
LChckInd StrucName2, FldPoint, ebx, edx, dl, ExErr
Замечание. После входа в подсписок (тип Б) перемещения внутри него должны происходить с помощью макросов LGetNext и LGetPred. В списках типа "В" — с помощью LGetSubNext и LGetSubPred.
Проверка попадания индекса в пределы таблицы ссылок
Изменяемые регистры: Flags
Особенности работы: Если ExErr не указан, то после выполнения макроса флаги установлены таким образом, что по ко- манде 'ja xxx' будет выполнен переход в том случае, если индекс выходит за пределы таблицы, а по команде 'jbe xxx', если не выходит. Если размер памяти, отводимой под таблицу ссылок, выделяется динамически, т. е. перед таблицей имеется поле размера, и метка ExErr не указана, то проверке подвергается только младший байт регистра, что может быть использовано в тех случаях, когда заведомо известно, что индекс помещённый в IReg не превышает 255 (например, когда индекс заносится командой movzx).
LChckInd macro SN:REQ, Fld:REQ, Reg:=<ebx>, ExErr, IReg:REQ, LIReg:REQ
Макрос устанавливает ссылки в элементе списка так, как если бы он был первым и единственным элементом в списке
LMakeFirstEl macro SN:Req, Reg:=<ebx>
LMakeSubFirstEl macro SN:Req, Fld:Req, Reg:=<ebx>, IReg:Req
Проверяет, является ли элемент последним в списке или подсписке. В линейном списке элемент последний, если ссылка на последующий элемент пустая, В кольцевом списке элемент последний, если он указывает сам на себя.
Вход:
LChckLast macro SN:REQ, Reg:=<ebx>
LChckSubLast macro SN:REQ, Fld:req, Reg:=<ebx>, IReg:=<0>
Проверяет, является ли элемент первым в списке (подсписке). Используется только для линейных двунаправленных списков. Если ссылка на предыдущий элемент пустая, то элемент — первый.
Вход: Reg — регистр, указывающий на элемент, по умолчанию [ebx] IReg — индексный регистр, содержащий номер интересующей ссылки в таблице ссылок Fld. Необходим и используется только в том случае, когда Fld является не единичной ссылкой, а таблицей ссылок. Может быть также сме- щением ссылки в Fld. Выход: Flags — Если элемент первый, то флаги установлены так, что по команде "je xxx", выданной после макроса, будет выполнен переход по метке "xxx".LChckFirst macro SN:REQ, Reg:=<ebx>
LChckSubFirst macro SN:REQ, Fld:req, Reg:=<ebx>, IReg:=<0>
Проверяет размер затребованной ОЗУ и, если надо, корректирует его. Если размер запрашиваемой ОЗУ меньше 8 байт, то он приравнивается 8 байтам.
Вход: ReqSize — регистр или память, содержащий затребованный объём ОЗУ
Выход:
LCorrectSize macro ReqSize:req, JNoCorrect
© Жуков И. Б.
e-mail: ibzh@yandex.ru
При использовании материалов, пожалуйста,
ставьте индексируемую ссылку на сайт https://ibzh.eko3.ru/
Число посетителей | |||
| Число посетителей |