Из журнала Scenergy #2, Новгород, 2000 (C) Flying/DR Memory Management Library 0. Вступление В этой статье речь пойдет об одной, на мой взгляд, очень нужной всем вещи - моей макробиблиотеке Memory Management Library. Хм... Судя по началу, от скромности я не умру :) Но у меня есть основания говорить подобные вещи т.к. то, о чем пойдет речь в данной статье действительно может сильно облегчить жизнь кодеру. Ну ладно, хватит себя хвалить - перейдем к делу. 1. Общие сведения Итак, как вы уже могли заметить - Memory Management Library - это библиотека. Но не простая, а макробиблиотека. А это значит, что использование этой библиотеки никак не отразится на вашем коде т.к. в ней нет ни одной строчки, которая бы компилировалась в исполняемый код. "Зачем же она тогда нужна?", спросите вы. "А затем!" - отвечу я вам :) Затем чтобы облегчить выполнение ряда задач с которыми кодер сталкивается практически в каждой своей программе. А именно: - распределение памяти - расчет экранных адресов - создание таблиц смещений или указателей - задание полей структур данных Все эти задачи решаются с помощью набора макросов, содержащихся в этой библиотеке. Т.е. они просто помогут вам организовать ваш код таким образом, чтобы он легко мог быть при желании модифицирован без лишних трудов по пересчету чисел в соответствии с изменившимися начальными данными. У этой библиотеки есть еще одно, на мой взгляд, большое достоинство - в отличие от всего остального моего кода, который компилируется только в TASM v4.12 и нигде больше, эта библиотека портирована мной и в ALASM v4.2! Т.е. пользователи ALASM'а тоже могут использовать все преимущества работы с использованием этой библиотеки не пересаживаясь никуда из своего любимого ассемблера. Более того, если в каком-либо другом ассемблере появится поддержка макросов - эту библиотеку можно будет без проблем перенести и туда. Далее вы найдете подробные описания всех частей этой библиотеки, а также примеры ее использования. В приложении вы найдете сам исходный текст библиотеки в форматах TASM4 и ALASM. Кроме того, в качестве примера ее использования можно посмотреть исходники ZXA library. Но перед тем как начать описывать саму библиотеку, я должен сделать замечание, касающееся ALASM'а. Дело в том, что в этом ассемблере отсутствует функция DISPLAY (не понимаю, почему KVA/E-Mage ее не сделал), а эта функция используется в библиотеке для выдачи предупреждений о переполнении памяти при компиляции. Поэтому есть одно различие в работе этой библиотеки в TASM'е и в ALASM'е: в ALASM'е макрос DISPLAY_INFO просто создает переменные, содержащие статистическую информацию о распределении памяти в программе, тогда как в TASM'е он, кроме того, выводит ее на экран, а также, при необходимости, выдает предупреждения о перерасходе памяти. Но это единственное различие в работе этих версий библиотек, в остальном их работа идентична. А теперь непосредственно описания. 2. Распределение памяти Это, несомненно, самая важная часть этой библиотеки. Можно даже сказать, что все остальное - не более чем довесок к этой основной функции, для которой библиотека разрабатывалась изначально. Если вы читали в Scenergy#1 мою статью "О пользе макросов", то вы наверняка помните, что я уже упоминал в ней о возможности использования макросов для решения задачи распределения памяти в программе. Более того - в приложении, в архиве MusSync.Z, можно было найти файл MEMORY.A который, по сути, являлся первой версией библиотеки. К сожалению, та версия была строго заточена под одну-единственную модель распределения памяти и не могла быть использована как универсальная библиотека. Более того, в ней, как оказалось, есть баг, о котором я узнал буквально сегодня, после почти 2 лет ее использования :) Итак, что же позволяет эта библиотека? 1) Она позволит вам выделять блоки памяти в вашей программе. При этом можно выделить неограниченное количество блоков. Размеры блоков также ничем не ограничены. Кроме того, можно выделять блоки с выравниванием адреса по границе #100 байт (так, чтобы адрес выделенного блока имел адрес вида #xx00, где #xx - любое число). 2) Она имеет два режима работы: можно рассматривать всю доступную память как один большой кусок или разделить ее на медленную (SLOW) и быструю (FAST). Кроме того, отдельным блоком рассматривается впечатанная в основную память страница. 3) Можно выделять память обычным способом (от младших адресов к старшим) или по принципу стека (от старших к младшим). 4) Местоположение и размеры любого куска памяти в котором будет выделяться память можно настроить. Способ выделения памяти (см. п.3) также настраивается для каждого блока. 5) После компиляции программы все данные о статистике выделения памяти могут быть получены из специальных переменных. Для TASM'овской версии библиотеки эти данные также выводятся непосредственно на экран. 6) В TASM'овской версии библиотеки также реализован контроль за выделением памяти. В случае если выделенные блоки памяти "залезают" на код или данные - выдается соответствующее предупреждение. Кроме того можно контролировать "залезание" кода или данных в страничную память. К сожалению, последний пункт реализован только в TASM'е т.к. ALASM не поддерживает функцию DISPLAY. Поэтому пользователям ALASM'а придется вручную контролировать переполнение при выделении памяти. Ну, или доделать в ALASM'е эту функцию :) А теперь подробнее обо всех перечисленных выше функциях. 2.1 Макросы для выделения памяти Как уже было сказано - есть два режима работы библиотеки в зависимости от того, какая схема распределения памяти вам нужна в вашей программе: - Вся память одним большим куском. Этот режим подходит, если, например, ваша программа располагается с адреса #6000, а память выше нее нужно использовать для выделения блоков памяти. - Память разделена на SLOW и FAST. Этот режим подходит, если ваша программа пишется с учетом наличия компьютеров с "медленной" памятью. В этом случае код обычно располагается с адреса #8000, область #6000-#7FFF рассматривается как "медленная" память и используется под хранение таблиц, а вся память до #C000 считается "быстрой" и используется под создаваемые процедуры. Замечу, что в обоих режимах страничная память (#C000-#FFFF) рассматривается как отдельный блок UPPER memory. Обычно это бывает необходимо т.к. программы сейчас обычно пишутся под 128кб памяти и нужно рассматривать страничную память отдельно. Однако, если ваша программа не требует переключения страниц - этот блок можно объединить с каким-нибудь другим. Но вернемся к режимам работы библиотеки. Режим работы задается значением переменной USE_COMMON_MEMORY. Она может принимать два значения: YES и NO. Если USE_COMMON_MEMORY=YES, то библиотека будет работать в первом режиме (с общим блоком памяти), если NO, то память будет разделена на SLOW и FAST. По умолчанию USE_COMMON_MEMORY=NO. Как можно заметить из всего написанного выше - всего, во всех режимах, существует 4 блока, в которых можно выделять память: - Общая (COMMON) - "Медленная" (SLOW) - "Быстрая" (FAST) - Страничная (UPPER) Кроме того, как уже было сказано - можно также выделять память с выравниванием адреса по границе #100 байт. Т.е. всего в библиотеке существуют 8 макросов. Их имена составлены по схеме: GET_xxxx_MEMORY - для выделения памяти. GET_xxxx_MEM_XX00 - для выделения памяти с выравниванием адреса по границе #100 байт (маска адреса - #xx00). Здесь "xxxx" - любой из идентификаторов блока памяти (COMMON, SLOW, FAST, UPPER). Единственное исключение - идентификатор COMMON пропускается. Т.е. заданы следующие имена макросов: Для выделения памяти Для выделения памяти с выравниванием адреса GET_MEMORY GET_MEM_XX00 GET_SLOW_MEMORY GET_SLOW_MEM_XX00 GET_FAST_MEMORY GET_FAST_MEM_XX00 GET_UPPER_MEMORY GET_UPPER_MEM_XX00 Однако их работа зависит от выбранного режима работы библиотеки: Если USE_COMMON_MEMORY=NO, то макросы GET_MEMORY и GET_MEM_XX00 ничего не делают т.к. блок памяти для них в этом режиме просто не существует. В TASM'овской версии библиотеки при попытке использования этих макросов в данном режиме будет выдано на экран предупреждение. В ALASM'вской версии кодеру самому придется контролировать правильность задания режима работы т.к. в противном случае память просто не будет выделяться и программа, естественно, будет неработоспособной. Если USE_COMMON_MEMORY=YES, то все вызовы макросов для SLOW и FAST блоков будут переадресованы к COMMON блоку. Т.о. можно выделять память через вызовы GET_SLOW_xxxx и GET_FAST_xxxx и не заботиться о том, что в случае чего придется переписывать код. Именно так написана библиотека ZXA library и поэтому ее можно спокойно использовать в обоих режимах работы библиотеки - память все равно будет выделена правильно. Общий формат вызова макросов: GET_xxxx_MEMORY , GET_xxxx_MEM_XX00 , Здесь - метка для выделяемого блока памяти, - размер блока в байтах. Метка создается автоматически, так что создавать ее вручную через EQU не нужно. Для задания можно использовать любое выражение в стандартном синтаксисе ассемблера. 2.2 Макросы для конфигурирования памяти Кроме режима работы на результаты работы библиотеки также непосредственно влияет конфигурация памяти. Конфигурация памяти подразумевает задание местоположения и размеров тех областей памяти которые будут использоваться для выделения памяти, а также способа выделения памяти в каждом из этих участков. Необязательно задавать эти данные для всех блоков памяти, достаточно задать их только для тех блоков, которые вы реально будете использовать в своей программе. Например, если ваша программа располагается с адреса #6000, а вся память выше нее свободна и может использоваться для выделения памяти, то достаточно будет определить данные только для COMMON блока памяти. Конфигурация памяти также производится с помощью макросов. Их 4, по числу заданных в библиотеке блоков памяти: SET_MEMORY - для COMMON memory SET_SLOW - для SLOW memory SET_FAST - для FAST memory SET_UPPER - для UPPER memory Общий формат вызова макросов: SET_xxxx ,, Здесь - адрес нижней границы области памяти, которая будет использована для выделения памяти, - адрес верхней границы области памяти. Последний параметр определяет, как будет выделяться память: YES - память будет выделяться по принципу стека - от старших адресов к младшим. NO - память будет выделяться от младших адресов к старшим. Необходимо заметить, что в реально задается адрес байта, следующего непосредственно за выделенным блоком памяти. Т.е. например вызов: SET_MEMORY #6000,#8000,YES выделит блок памяти в адресах #6000-#7FFF с распределением памяти по принципу стека. За счет использования параметра задающего принцип распределения памяти вы сможете еще гибче настроить схему распределения памяти в ваших программах. Например, если блок памяти для выделения располагается непосредственно за кодом вашей программы, то вам явно будет удобно, чтобы память распределялась, двигаясь "навстречу" коду, т.е. по принципу стека. А вот нижнюю или страничную память удобнее иметь с обычной схемой распределения. Да, еще необходимо рассказать о том, как сконфигурирована память по умолчанию, и как ее можно поменять. Конфигурация памяти по умолчанию выглядит следующим образом: +----------------+------+-----+------------+ |Тип блока памяти|Начало|Конец|Схема работы| +----------------+------+-----+------------+ |Common Memory |#6000 |#C000|Stack | |Slow Memory |#6000 |#8000|Normal | |Fast Memory |#8000 |#C000|Stack | |Upper Memory |#C000 |#0000|Normal | +----------------+------+-----+------------+ Посмотрев исходный код библиотеки, вы увидите, что эти данные заданы в метках, чьи названия составлены по принципу: <Название блока>_LOW <Название блока>_HIGH <Название блока>_STACK Так что если вы в своих программах обычно используете другую конфигурацию памяти - ее лучше всего настроить прямо в коде библиотеки, чем каждый раз использовать макросы настройки памяти. Есть только один нюанс: при изменении заданной по умолчанию конфигурации памяти вам придется также переписать макрос DISPLAY_INFO т.к. он написан с учетом именно той конфигурации, которая задана. В противном случае я не гарантирую, что он будет работать правильно. 2.3 Контроль за распределением памяти. Механизм распределения памяти был бы не совсем удобен в использовании, если бы не было также механизма для контроля за распределением памяти. В Memory Management Library за этот контроль отвечает макрос DISPLAY_INFO. С его помощью вы сможете получить следующую информацию: - Размер кода программы. - Объем подгружаемых данных. - Общий размер программы. - Объемы оставшейся свободной памяти в каждом блоке памяти. - (*) Сообщения о перерасходе памяти при выделении. - (*) Сообщение о "залезании" выделенной памяти на код или подгружаемые данные. - (*) Сообщение о "залезании" кода или подгружаемых данных на страницу. Последние 3 пункта, помеченные знаком (*) работают только в TASM'овской версии. Это, как я уже говорил, объясняется отсутствием в ALASM'е функции DISPLAY. Кроме того, этот макрос может также сам заносить в STS адрес запуска программы, с тем, чтобы при выходе в STS вам не нужно было вручную устанавливать адрес запуска, перед тем как начать отладку программы. Как видите, этот макрос является довольно полезным. Однако его использование требует выполнения нескольких простых правил с тем, чтобы макрос мог получить необходимую для него информацию. Я приведу схему программы, какой она должна быть для того, чтобы этот макрос работал правильно. Необходимые для его работы строчки выделены желтым цветом. Я использую синтаксис TASM'а, но для ALASM'а все аналогично. .INCLUDE MEMORY START_ADR=#8000 ;Адрес компиляции и запуска программы ORG START_ADR ;Здесь располагается код программы END_OF_CODE=$ ;Здесь располагаются подгружаемые данные END_OF_DATA=$ DISPLAY_INFO Как видите - для работы макроса требуется задание 3 переменных: START_ADR - Адрес компиляции и запуска программы. END_OF_CODE - Конец кодового блока вашей программы. Начиная с этого места располагаются данные. END_OF_DATA - Конец блока данных. Кроме того, программа должна выполнять еще 2 условия: - Адрес компиляции программы должен быть равен адресу запуска. Для большинства программ это так и есть. - Блок подгружаемых данных должен быть расположен непосредственно за кодовым блоком программы. Это тоже справедливо для большинства программ. Необходимо пояснить, что я подразумеваю под термином "подгружаемые данные". Это те данные, которые, как правило, подгружаются к программе по .INCBIN, а после запуска программы перекидываются LDIR'ом на свое место в памяти. Например, обычно музыка, спрайты или таблицы располагаются где-то в памяти (или в страницах), но их обычно линкуют вместе с кодом, чтобы образовать один моноблок, а затем, после запуска их перебрасывают на то место, где они должны располагаться. Вот этот блок данных я и называю "подгружаемые данные". Важность их в том, что они существуют только на этапе запуска программы, а затем эту память уже можно использовать подо что-то другое. Эта специфика позволяет библиотеке, во-первых, использовать эту память для распределения, а во-вторых, отдельно контролировать то, что выделенная память "залезает" на данные и предотвращать возможные конфликты из-за использования одного и того же участка памяти. Проще говоря, считается, что блок памяти от START_ADR до END_OF_CODE трогать нельзя а блок от END_OF_CODE до END_OF_DATA можно использовать для распределения памяти, но в случае, если выделенная память "залезет" в этот блок - нужно выдать предупреждение. Есть, правда во всем этом одно "НО". Дело в том, что существует определенная разница в работе макроса DISPLAY_INFO в случаях, когда программа использует заданную по умолчанию схему распределения памяти или же создает свою. Библиотека контролирует изменения конфигурации памяти и запрещает некоторые функции макроса DISPLAY_INFO. В случае использования собственной схемы распределения памяти будут отсутствовать следующие функции: - автоматическая подгонка размера блока Fast Memory, с тем, чтобы он занимал всю память от последнего байта кода до #C000. - контроль за "залезанием" блоков памяти, выделенных в Fast Memory на подгружаемые данные. Помимо своей основной функции - контроля за переполнением памяти, DISPLAY_INFO еще имеет ряд дополнительных функций. 1) Он может помочь в случае, когда вы уже написали и отладили программу и хотите сохранить на диске ее код. В ALASM'е такая функция отсутствует, а в TASM'е она работает только при наличии в программе только одного ORG'а, поэтому обычно код сохраняется из STS. А для этого необходимо знать 2 вещи: стартовый адрес блока кодов и размер этого блока. Вот здесь-то вам и пригодится макрос DISPLAY_INFO. В Memory Management Library определена метка FINAL_RELEASE. Обычно я использую ее в своих программах для того, чтобы убрать отладочный код при компиляции финального релиза программы. Т.е. отладочный код в программе пишется так: .IF FINAL_RELEASE-NO ;Здесь кусок отладочного кода .ENDIF А в самом начале программы (обычно в main file) помещается следующая конструкция (в синтаксисе TASM'а): .IF [#5C08]-#0E FINAL_RELEASE=YES .ENDIF Обычно при компиляции отладочной версии эта метка не переопределяется (ее значение по умолчанию равно NO), а когда необходимо откомпилировать финальный вариант кода, при компиляции просто нажимается клавиша EXT.MODE - и без редактирования исходника я получаю программу без отладочного кода. Так вот, кроме этой метки в библиотеке также определены метки SAVE_START и SAVE_END. Если использовать их следующим образом: SAVE_START=$ ;Здесь блок кода, который должен быть ;выгружен на диск. SAVE_END=$ то при компиляции финального релиза вашей программы, кроме всей прочей информации, будут выведены на экран (для TASM'а) или просто определены (для ALASM'а) метки: SAVE_START - адрес блока для записи. SAVE_SIZE_ - размер этого блока. Выйдя в STS, вы сможете спокойно записать необходимый вам блок кода на диск. Кстати, по умолчанию (если эти метки не переопределять) они будут автоматически установлены в следующие значения: SAVE_START=START_ADR SAVE_SIZE_=END_OF_DATA Так что в большинстве случаев вы получите данные ни о чем не заботясь. 2) Макрос DISPLAY_INFO может "патчить" STS с тем, чтобы при выходе в нее адрес PC был равен адресу запуска программы. Для использования этой функции задайте в переменной STS_BANK номер страницы, где у вас лежит STS, например STS_BANK=#17. Если же эта функция вам не нужна - не делайте ничего, по умолчанию никаких патчей не производится. 3) Эта функция есть только в TASM'овской версии библиотеки. Обычно программы, использующие страничную память, должны располагаться ниже #C000, чтобы не было глюков в случае, если будет включена не та страница. То же касается и подгружаемых данных - если они "залезут" в страничную память, то их будет не так-то просто перекинуть в другую страницу. Все эти вещи автоматически отслеживаются кодом библиотеки и в случае если что-то все же "залезло" на страницу - будет выдано соответствующее предупреждение. Если же ваша программа, по каким-либо причинам, не требует подобных проверок - можно просто установить переменную CODE_ON_PAGES=YES, и эти проверки будут отключены. Пользователи ALASM'а такой возможности лишены опять-таки из-за отсутствия в ALASM функции DISPLAY. И еще одно. Как правило для блока памяти, расположенного, непосредственно за кодом программы, нельзя сказать заранее - какими будут его границы т.к. размер блока кода заранее неизвестен. Здесь вам поможет тот факт, что непосредственно контроль за переполнением памяти при выделении будет осуществляться только при вызове макроса DISPLAY_INFO, т.е. в самом конце. Поэтому для блока, расположенного непосредственно за кодом программы лучше всего задать четко только верхнюю границу, а нижнюю можно задать как угодно. Кроме того, нужно задать для этого блока схему распределения памяти по принципу стека (чтобы код и выделенная память двигались навстречу друг другу). Выделив все необходимые блоки как обычно, в самом конце программы добавьте одну-единственную строчку (на примере FAST блока): FAST_LOW=END_OF_CODE Т.о. вы точно установите размеры блока на всю имеющуюся память. Также можно делать и с любым другим блоком памяти - контроль переполнения памяти будет работать верно. 2.4 Примеры использования Приведу парочку примеров выделения памяти в блоках с normal и stack схемами работы. Напомню, что блок Slow располагается по адресам #6000-#8000 и работает с normal схемой, а блок Fast расположен по адресам #8000-#C000 и выделяет память по принципу стека. GET_SLOW_MEMORY TABLE_1,#A0 GET_SLOW_MEMORY TABLE_2,#120 GET_SLOW_MEM_XX00 TABLE_3,#200 При стандартной конфигурации памяти эти вызовы приведут к следующим результатам: TABLE_1 EQU #6000 TABLE_2 EQU #60A0 TABLE_3 EQU #6200 ;А не #61C0!!! Аналогичные выделения памяти для FAST memory: GET_FAST_MEMORY TABLE_1,#A0 GET_FAST_MEMORY TABLE_2,#120 GET_FAST_MEM_XX00 TABLE_3,#200 Дадут следующие результаты: TABLE_1 EQU #BF60 TABLE_2 EQU #BE40 TABLE_3 EQU #BC00 ;А не #BC40!!! Надеюсь что все здесь просто и понятно. 3. Расчет экранных адресов Макросы, относящиеся к этой группе должны облегчить кодеру решение задачи, с которой он сталкивается практически в каждой своей программе. Я говорю о расчете экранных адресов. Ни для кого не секрет, что из-за достаточно "оригинальной" структуры экрана Speccy задача расчета экранного адреса по координатам является отнюдь не тривиальной и не каждый кодер может быстро провести этот пересчет в уме. Задавать же экранные адреса в программах приходится достаточно часто, причем в разных вариантах - как в виде таблицы, так и в виде EQU. На все эти случаи в Memory Management Library есть свои макросы. Все они делают одно и то же, разница заключается только в том, куда помещается результат. Имена макросов составлены по следующему принципу: _xxxx_SCRADR - расчет экранных адресов _xxxx_ATTRADR - расчет адресов в атрибутах Здесь xxxx может иметь 2 значения: GET и DEFW. В зависимости от этого изменяется набор параметров для макроса: _GET_SCRADR <Метка>,, _DEFW_SCRADR , При этом после вызова макроса _GET_SCRADR метке, переданной в качестве параметра, будет присвоено значение экранного адреса. При вызове макроса _DEFW_SCRADR экранный адрес будет помещен в память по текущему адресу компиляции. Формат вызова макросов для расчета адреса в атрибутах аналогичен. Значения X и Y координат зависят от того, что рассчитывается: адрес на экране или в атрибутах. Для расчета экранного адреса X задается в знакоместах, а Y - в пикселях. Для расчета адреса в атрибутах и X и Y задаются в знакоместах. Использование макросов предельно простое: _GET_SCRADR SCREEN_ADR,5,9 ;Результат: SCREEN_ADR=#4125 _GET_ATTRADR ATTR_ADR,5,9 ;Результат: ATTR_ADR=#5925 Или пример составления таблицы экранных адресов: _DEFW_SCRADR 0,0 _DEFW_SCRADR 0,1 _DEFW_SCRADR 0,2 ... _DEFW_SCRADR 0,191 Кроме того, есть еще один набор макросов, выполняющий те же самые функции, но для экрана, расположенного с адреса #C000. Их имена составлены по тому же принципу, но с добавлением в конец буквы 'H' (от High): _GET_SCRADRH _GET_ATTRADRH _DEFW_SCRADRH _DEFW_ATTRADRH Первоначально в TASM'овской версии этих макросов не было. Можно было просто задать дополнительный параметр при вызове макроса для расчета адреса для нижнего экрана - и расчет бы производился для экрана с адреса #C000. Например: _DEFW_SCRADR 10,8 ;Значение #402A _DEFW_SCRADR 10,8,1 ;Значение #C02A Это намного удобнее, но, к сожалению, в отличие от TASM'а, ALASM не поддерживает макросов с переменным числом параметров (хотя KVA/E-Mage и говорил, что сделал все "один в один как в TASM'е"). Поэтому, из соображений совместимости, пришлось делать еще 4 макроса. 4. Создание таблиц указателей и структур данных Эта группа макросов предназначена для решения двух типов задач перечисленных в заголовке. Действительно, практически в каждой программе найдется несколько таблиц указателей или структур данных. Обычно их задают в виде кучи чисел, которые затем, обычно, затруднительно исправлять. Этот набор макросов поможет вам справиться с этим наилучшим способом. Всего в этом разделе находится 3 макроса, различающихся только тем, куда и в каком виде помещается результат: _SZ <Метка>,<Размер> _SZ_DEFB <Размер> _SZ_DEFW <Размер> Понятно, что макрос _SZ присваивает новое значение метке, а макросы _SZ_DEFB и _SZ_DEFW помещают в память соответственно байт и слово. Все эти макросы используют для своей работы одну и ту же переменную (___ORG), которая является указателем текущего значения. Поэтому перед каждым новым использованием любого из макросов этой группы вам необходимо будет заново инициализировать эту переменную новым текущим значением. И, естественно, во избежание недоразумений, нельзя смешивать вызовы разных макросов этой группы без переинициализации этой переменной. Попытаюсь вам продемонстрировать, как же использовать эти макросы. Я приведу пару примеров дающих одинаковый результат: один вариант в стандартном синтаксисе, а другой с использованием этих макросов. Пример 1. Создание таблицы указателей на спрайты. SPRITES - указатель на набор спрайтов, SPR_SZ - размер одного спрайта. Стандартный синтаксис: SPRITES_TABLE DEFW SPRITES+(0*SPR_SZ) DEFW SPRITES+(1*SPR_SZ) ... DEFW SPRITES+(2*SPR_SZ) С использованием макросов: ___ORG=SPRITES SPRITES_TABLE _SZ_DEFW SPR_SZ _SZ_DEFW SPR_SZ ... _SZ_DEFW SPR_SZ Заметьте, насколько удобнее будет потом, при необходимости, менять что-то в этой таблице, вставлять или убирать из нее элементы. Пример 2. Задание EQUS для полей структуры данных. Пусть структура данных имеет такой вид: STRUCTURE DEFB 0 ;BYTE_FIELD_1 DEFB 0 ;BYTE_FIELD_2 DEFW 0 ;WORD_FIELD_1 DEFB 0 ;BYTE_FIELD_3 DEFS 10,0 ;TABLE_FIELD_1 DEFS 25,0 ;TABLE_FIELD_2 DEFW 0 ;WORD_FIELD_2 Задание EQUS для полей этой структуры данных в стандартном синтаксисе: BYTE_FIELD_1 EQU 0 BYTE_FIELD_2 EQU BYTE_FIELD_1+1 WORD_FIELD_1 EQU BYTE_FIELD_2+1 BYTE_FIELD_3 EQU WORD_FIELD_1+2 TABLE_FIELD_1 EQU BYTE_FIELD_3+1 TABLE_FIELD_2 EQU TABLE_FIELD_1+10 WORD_FIELD_2 EQU TABLE_FIELD_2+25 Заметьте, насколько неудобно будет что-то изменять в этой структуре данных, особенно если что-то надо будет добавить в середину или удалить. А теперь то же самое, но с использованием макросов: ___ORG=0 _SZ BYTE_FIELD_1 ,1 _SZ BYTE_FIELD_2 ,1 _SZ WORD_FIELD_1 ,2 _SZ BYTE_FIELD_3 ,1 _SZ TABLE_FIELD_1,10 _SZ TABLE_FIELD_2,25 _SZ WORD_FIELD_2 ,2 Сравните наглядность и удобство задания! А уж про удобство редактирования можно просто умолчать :) Думаю, что приведенные примеры говорят сами за себя. 5. Заключение Вот, собственно, и все. Смешно, конечно, что описание библиотечки с объемом порядка 400 строчек ассемблера занимает почти 30кб текста, но это говорит только о том, что возможности этой библиотеки, несмотря на малый объем, действительно велики (ну или о том, что я не умею кратко излагать свои мысли :) ). Я очень надеюсь, что данная библиотека поможет вам в работе. Конечно, с первого взгляда, она может показаться чем-то непривычным, но поверьте мне - она на самом деле очень сильно облегчает работу! Happy coding!