Реферат: Как претворить в жизнь идею компьютерной игры? Приходилось ли вам, играя в свою любимую игру, мечтать о том, как можно было бы ее улучшить


ВВЕДЕНИЕ

Как претворить в жизнь идею компьютерной игры? Приходилось ли вам, играя в свою любимую игру, мечтать о том, как можно было бы ее улучшить? Задумывались ли вы в процессе игры о том, как она устроена? Эта книга откроет вам путь к созданию собственных игр.

Компьютерные игры

Если вы не играли в свою любимую аркадную игру и не убивали кучу монстров уже несколько месяцев — вы явно заработались! Вернитесь к играм и увидите, как вы помолодеете и как разгладятся морщины на вашем утомленном лице. Дни Hunt the Wampus и Lunar Lander миновали, и теперь игры стали намного ярче, красочней и хитрее.

Игра сегодняшнего дня содержит целые мили туннелей, дорог и странных созданий. Они сопровождаются потрясающей графикой и звуком. DOOM - хороший пример такой игры. Она включает запутанные переходы и тайники с сокровищами, чудовищ, на которых вы можете охотиться и которых вы должны убить прежде, чем они убьют вас. Если вы хоть раз играли в DOOM, то не перестанете тосковать по кислотным лужам, ружьям, чудовищам, скрытым проходам, лифтам, взрывам... этот список можно продолжать до бесконечности. Все это было создано благодаря комбинации графики и звукового сопровождения.

Графика DOOM дает вам полное ощущение пространства и перспективы — как если бы вы действительно находились в центре событий. Для этого DOOM использует приемы работы с трехмерной графикой. Разработчики игр для ПК используют трехмерную графику для увеличения реалистичности игры любого типа — посмотрите на 7th Guest, MYST, X-Wing, Outpost, Indy Car Racing. Часто трехмерная графика применяется и в имитаторах спортивных игр, таких как скачки или бокс.

После добавления к такой графике сюжета и высококачественного звука, разработку игры можно считать законченной. Вы почувствуете себя реальным участником действия — стрельбы, гонок и поиска сокровищ, прилагающим все усилия, чтобы остаться в живых.

Об этой книге

Большинство книг по компьютерным играм объясняют, как в них играть. В отличие от них, эта книга учит, как создавать собственные игры, используя опыт, накопленный профессионалами. Профессиональные разработчики игр для ПК научат вас, как создать игру, использующую высококачественную графику и звук. Если вы только начинаете карьеру разработчика компьютерных игр, эта книга окажется для вас незаменимым подспорьем. Она научит вас, как осуществлять прокрутку графики, оформлять таблицы лучших результатов, программировать трехмерную графику, создавать текстуры и многому, многому другому. Если вы уже имеете опыт в разработке игр, эта книга поможет вам освоить массу новых технических приемов.

Что вы должны знать

Для понимания большинства материала, представленного в этой книге, вы должны хорошо владеть языком программирования Си. Большинство книг по созданию компьютерных игр ориентируются на ассемблер. В этой книге ассем­блеру уделено совсем немного внимания, а основной акцент сделан на Си. Однако, она содержит несколько ассемблерных примеров, так что знакомство с этим языком вам не помешает.

Как организована эта книга

Эта книга посвящена технике разработки игр для ПК. Она не является учебником, в котором материал представлен последовательно — от простого к сложному, однако в построении книги заметна определенная система.

В первой главе, «Видеоигры, первые шаги...», вы увидите, кто пишет игры, откуда берутся идеи и изучите основные этапы разработки игровой программы,

Вторая глава, «Основы языка ассемблера», даст вам небольшой урок по ассемблеру. Вы изучите терминологию и методы, использованные при создании игр, представленных в этой книге.

В третьей главе, «Основы устройств ввода», будет дан обзор методов построения интерфейса между играми и такими устройствами, как джойстик, мышь и клавиатура.

Четвертая глава, «Механизмы двухмерной графики», покажет вам основные приемы, используемые при разработке двухмерных игр типа Commander Keen, Вы изучите технику вращения, масштабирования, перемещения и отсечения объектов в двухмерных играх.

Глава пятая, «Секреты VGA-карт», посвящена работе с графикой VGA. Вы изучите 256-цветный режим, таблицу цветов, копирование битовых изображений и т. д.

Шестая глава, «Третье измерение», укажет вам путь в трехмерный мир- Вы узнаете, как создавать DOOM-подобные миры, используя технику работы с трехмерной графикой.

Седьмая глава, «Усовершенствованная битовая графика и специальные эффекты», позволит вам получить представление об ускоренном выводе изобра­жений, работе со светотенью и другими специальными эффектами. Будьте внимательны — отсюда начинаются боевики!

Глава восьмая, «Высокоскоростные трехмерные спрайты», откроет вам дорогу в изумительный мир высокоскоростных трехмерных спрайтов. Эта глава покажет, как создать реалистичную модель космического корабля и вставить ее в вашу программу.

В девятой главе, «Звуковые эффекты и музыка», вы познакомитесь с основами применения звука в ваших играх. Вы сможете вызвать у игрока волнение и ужас, используя Sound Blaster. В игре музыка заставит ваше сердце биться в такт происходящему.

В десятой главе, «Разработка музыки для компьютерных игр» вы изучите приемы работы с программами DIGPAK и MIDPAK, которые позволяют создавать впечатляющую музыку и звуковые эффекты. Благодаря этим программам вы сможете добавлять оркестровую музыку и необычайные звуковые эффекты в ваши творения, что придаст им профессиональный вид. Эти программы использовались при создании таких популярных игр, как 7th Guest, Terminator 2029, Mechwarrior II.

Глава одиннадцатая, «Алгоритмы, структуры данных и методология видеоигр», знакомит вас с внутренней структурой профессиональных игр. Вы научитесь моделировать реальный мир в своих виртуальных вселенных.

Двенадцатая глава, «Мнимое время, прерывания и многозадачность», научит вас добавлять новое измерение — время. Вы изучите приемы параллельной обработки различных игровых задач, создания главного игрового цикла и как использовать прерывания при работе с устройствами ввода/вывода.

Глава тринадцатая, «Искусственный интеллект», поможет вам сделать своих персонажей более интеллектуальными. Вы же не хотите, чтобы они напрасно теряли время. Научите их думать и реагировать на ваши действия так, чтобы игру невозможно было отличить от реальности.

Четырнадцатая глава, «Связь», научит вас добавлять дополнительные возможности. Вы сможете звякнуть по модему приятелям и отправиться вместе с ними по неисследованным дебрям виртуальных миров. Игры, рассчитанные на нескольких участников, становятся все более популярными.

Глава пятнадцатая, «Инструментальные средства», даст вам полный набор инструментов для построения ваших игр. С помощью этих инструментов можно создать текстуры, персонажей, звуковые файлы, планы игрового поля, декорации.

В шестнадцатой главе, «Графическое оформление игр», вы получите представление о точке зрения художника. Используя подходы, описанные в этой главе, вы сможете придать игре больший профессионализм и художественную ценность.

Семнадцатая глава, «Техника создания параллакса», учит, как заставить удаленные объекты двигаться медленнее, чем приближенные. Эта техника позволит оживить перспективу в ваших играх.

В восемнадцатой главе, «Техника оптимизации», демонстрируются технические приемы повышения производительности. Ваша игра будет работать быстрее и движения персонажей станут более плавными, если вы последуете приведенным в этой главе советам.

Девятнадцатая глава, "Игра Warlock (Колдун)", описывает игру, созданную автором этой книги.

^ ВИДЕОИГРЫ. ПЕРВЫЕ ШАГИ...

С чего начать? Хочется так много сказать, что невольно придется посвятить этому несколько страниц. То путешествие, которое мы собираемся предпринять в мир разработки видеоигр, можно смело назвать захватывающим приключением. Создание видеоигр можно сравнить с написанием стихов или рисованием картины. Для этого нужно вдохновение, ведь создатель хочет поделиться с окружающим миром частичкой своего воображения. Один великий скульптор сказал однажды; «Статуя была здесь всегда, Я просто освободил ее из камня». Это высказывание вполне применимо и к видеоиграм.

Компыотер — это просто хранилище битов информации и, устанавливая их в 1 или 0, вы создаете образ. В этом заключается искусство. Я хочу, чтобы вы настроились на созидательную работу. Нам потребуется полное взаимопонимание. В этой главе я расскажу о том, как создаются видеоигры. Вы узнаете вот о чем:

Кто пишет видеоигры;

Откуда берутся идеи;

Фазы создания видеоигры;

Что вы узнаете из этой книги.

В следующих главах вы узнаете, как писать игры.

Кто пишет видеоигры?

Видеоигры создаются группами совершенно разных людей. Нас объединяет желание сделать нечто, что заставляет смеяться, улыбаться, в азарте подпрыгивать на стуле. Если вы хотите доставить другим радость, то можете не сомневаться, что видеоигры - это то, что надо! Кроме того, создание видеоигр и нас самих делает счастливыми. Я не думаю, что написание компилятора может сделать кого-нибудь счастливым человеком.

Видеоигры - это способ выражения самых фантастических идей и образов. Многим просто необходима отдушина для воплощения своих безумных фантазий. Кстати, я думаю, что именно поэтому существует театр и кино. Мы верим в свои иллюзии, порой убегая в них от ужасной реальности. Видеоигры — это такой же способ позволить людям хотя бы на время стать богами своих маленьких вселенных.

Откуда берутся идеи?

Идеи видеоигр берутся из нашего воображения. Именно в нашем сознании существует бесчисленное количество миров, населенных роботами, и городов, наводненных призраками. Попробуйте покопаться у себя в голове и найти какой-нибудь сюжет. Если ничего не получится — не расстраивайтесь. Сходите в ближайший прокат видео и возьмите несколько фантастических лент — может быть они помогут разыграться вашей фантазии? Единственное, от чего я хотел бы вас предостеречь — это от переделки чужих игр. Во-первых, это нехорошо, а во-вторых, у вас могут просто появиться серьезные неприятности. В конце концов, ваши сны могут подсказать самые фантастические сюжеты для игр.

Когда у вас, наконец, появится идея, то очень важно дать ей "отстояться". Попробуйте в течение недели или двух просто выкристаллизовать ее у себя в голове. Пусть она станет для вас совершенно ясной, попробуйте играть в нее в своем воображении. Если это произойдет, то считайте, что самая сложная часть уже позади. Осталось самое простое - запрограммировать ваши идеи.

Фазы создания видеоигр

Видеоигра, как и любой другой программный продукт, должна создаваться по определенной методике. Это значит, что мы в процессе разработки должны придерживаться определенных правил и рекомендаций. Итак:

Во-первых, нужна идея. Мы уже об этом говорили;

Если есть понимание того, что будет в игре, то есть смысл написать что-то типа сценария. Если игра будет развиваться на нескольких уровнях — опишите каждый из них;

Затем вам надо разнообразить каждый из уровней какими-нибудь неожиданными ходами, целями и т. д. Вы должны заинтересовать игрока, заставить его проходить уровень за уровнем в вашей игре;

Если у вас есть понимание каждого уровня игры, то имеет смысл подумать о структуре самой игры. Как будут вести себя игровые объекты, как они будут взаимодействовать, какие возможности получит игрок?

В этот момент у вас уже есть достаточно информации, чтобы садиться и начинать писать более развернутый план игры. Теперь попробуйте чуть более заострить свое внимание на специфике игры. Например:

Выберите, в каком видеорежиме у вас будет работать игра. Например, она может быть выполнена в режиме высокого разрешения, но использовать при этом только несколько цветов.

Подумайте, насколько сложной будет графика. Будет ли она трехмерной или двухмерной.

О том, как решать эти проблемы вы также узнаете из данной книги.

Когда вы решите для себя эти вопросы, настанет время подумать о тех средствах созидания, которыми мы располагаем. Попробуйте начать конструировать с максимальной детализацией самый первый уровень. У вас сразу появится необходимость в специальных инструментальных средствах. Вот их минимальный набор:

Программа для рисования битовых образов;

Программа для анимации битовых образов;

Си-код для бит-блиттинга (блокового перемещения битовых образов), изменения видимого размера объектов (масштабирования) и рисования линий;

Алгоритмы искусственного интеллекта для персонажей игры;

Средства для работы со звуком;

Си-код для работы с устройствами, ввода;

Инструменты для рисования уровней и сохранения их на диске;

Наборы MIDI-звуков для каждого из уровней.

Когда вы начнете писать программу, старайтесь разбить ее на маленькие секции. На самом деле программа может быть разбита на следующие куски:

Игровой мир и описывающие его структуры данных;

Система рендеринга;

Система ввода/вывода;

Система искусственного интеллекта;

Основной игровой цикл; Интерфейс пользователя;

Система звука.

Система искусственного интеллекта;

Что вы узнаете из этой книги?

Эта книга написана, чтобы научить читателя создавать трехмерные видеоигры типа DOOM или Wolfenstein 3-D. Эту книгу не стоит рассматривать как учебник по видеографике. Книга написана так, что в ней освещаются вопросы, связанные именно с играми и разработкой игр.

Для того чтобы научиться писать игры, нам придется очень серьезно потрудиться, но я уверен, что когда вы закончите работу над этой книгой, то сможете самостоятельно разработать серьезную игру. Чтобы совсем быть в этом уверенным, мы с вами начнем писать игру. Эта такая трехмерная игра типа Wolfenstein 3-D. Впрочем, подойдя к последней главе, вы сами ознакомитесь с тем, что у меня получилось.

ИТОГ

В этой главе мы узнали кое-что о том, как и для чего пишутся видеоигры- Кроме того, выяснилось, что помимо технических навыков для разработки игры нужно иметь:

Сценарий игры;

Распределение по ролям;

Дизайн игры;

Понимание того, для чего вы все это делаете.

Итак, вроде, мы обсудили все, что находится вокруг игр. Пора начать работать. Давайте начнем.


^ ОСНОВЫ ЯЗЫКА АССЕМБЛЕРА

Доктор Руди Раккер, мой друг и бывший наставник, как-то сказал: «Ассемблер - это просто круто». Я думаю, эти слова как нельзя лучше подходят к данной главе. Ассемблер — это родной язык всех компьютеров, и если вы им хорошо овладеете, он предоставит вам фантастические возможности. В настоящей главе мы кратко ознакомимся с этим языком.

Мы научимся подключать фрагменты, написанные на ассемблере, к нашим программам и использовать встроенный (in-line) ассемблер компилятора Microsoft С. Кроме того, мы пойдем чуть дальше и напишем еще парочку графических процедур. Таким образом, эту главу можно разделить на следующие части:

Зачем нам нужен ассемблер при написании игр;

Описание семейства процессоров 80х86;

Регистры ЦПУ;

Общий вид процедуры на ассемблере;

Передача параметров;

Локальные переменные;

Создание внешних ссылок;

Возвращение параметров;

Некоторые полезные управляющие конструкции;

Установка видеорежимов;

Сверхскоростная очистка экрана;

Использование встроенного ассемблера.

Зачем нам нужен ассемблер при написании игр?

Даже для таких компьютерных богов, как Microsoft и Borland, сегодня ассемблер намного быстрее программ на Си. Я помню дни, когда все игры были написаны целиком на ассемблере. Вы можете себе это представить? К счастью сейчас у нас есть куча ученых и программистов, которые заняты разработкой компиляторов. Эти компиляторы дают код, не многим хуже ассемблерного. Но, к сожалению, даже компиляторы чуть-чуть не дотягивают до идеала. Вот почему нам обычно приходится последние пять процентов делать своими руками. Мы должны применять ассемблер для рисования точек, линий, выполнения заливок, наложений и т. д. Мы вынуждены его применять там, где нужна сверхскорость - в графике.

Существует несколько способов включения ассемблера в наши программы:

Можно написать на ассемблере отдельную функцию и вызывать ее из Си. Мы используем этот способ в критичных по времени выполнения участках программы;

Мы можем использовать такую штуку, как встроенный ассемблер. Он позволяет использовать инструкции ассемблера наравне с Си-кодом. Это неоценимая находка для разработчика видеоигр, поскольку вам не надо использовать кучу промежуточных утилит и создавать массу независимых текстов программ. Кроме того, встроенный ассемблер здорово экономит ваше время.

Прежде чем двинуться дальше, я хочу предостеречь вас от одной из крайностей - не надо пытаться написать на ассемблере все. Используйте его экономно и только тогда, когда он действительно нужен. В противном случае ваш код будет довольно сложен и непереносим (кстати, в том же самом DOOM'e на ассемблере написано всего несколько процедур, а все остальное — это эффективный Си-код). Если же вам надо написать более двух тысяч строк ассемблера, то лучшим решением будет пересмотр применяемого вашего алгоритма.

Обзор семейства процессоров 80x86

Семейство процессоров 80х86 более чем разнообразно. Фирма Intel создала процессоров больше, чем вы в состоянии представить. Игры и программы, которые мы будем создавать в этой книге, ориентированы только на 386-, 486- и 586-процессоры. Для наших целей мы будем говорить о реальном режиме работы этих процессоров: режим эмуляции процессора 8086, используемый DOS с 640-килобайтным ограничением.

Как вы знаете, первым из этого семейства был 8088. Он стоял на первом ПК. Потом его сменил 8086, но это уже история. Весь мир перешел на DOS которая изначально была ориентирована на процессор 8086. Производя процес­соры 286, 386, 486, 586 (и вроде скоро появится 686), Intel обеспечивал поддержку 8086-процессора. Это значит, что даже на 586 ПК вы неизбежно натолкнетесь в DOS'e на 640-килобайтный барьер.

На самом деле, это ужасно, поскольку 386 и все последующие 32-разрядные процессоры имеют непрерывную модель памяти (без сегментов), более мощные инструкции и т. д. Использование только новых инструкций сделает наши игры более мощными. Более того, непрерывная модель памяти устранит понятие сегментных регистров, 640-килобайтного барьера и всего, что с ним связано. Мы можем указать компилятору порождать 286-инструкции, дав соответствующую директиву, но это будут лишь 16-битные команды, которые не дадут нам возможности использования всех средств 32-разрядных процессоров типа 386 486 и 586.

Кстати, для удобства я буду называть 386-, 486- и 586-процессоры просто «процессорами».

В функции процессора входит перемещение данных в памяти и выполнение с ними некоторых преобразований. К ним относятся математические и логические (например, И, ИЛИ, НЕ) операции, проверка различных условий и т. п. Любой процессор должен иметь достаточно много регистров, чтобы его можно было эффективно программировать (они нужны для сохранения данных). Регистры процессора ПК будут описаны в следующем разделе.

Регистры процессора

^ Регистры общего назначения. Данные регистры используются во время выполнения программ и во многих случая являются взаимозаменяемыми. Кроме того, каждый из них имеет определенное предназначение.

АХ - 16 бит, общего назначения, часто именуется аккумулятором;

ВХ - 16 бит, общего назначения и индексный;

СХ - 16 бит, общего назначения и счетчик;

DX - 16 бит, общего назначения;

ВР - 16 бит, общего назначения, используется для хранения смещения и индексов, часто называется регистром базы;

SI - 16 бит, общего назначения, используется в операциях с памятью (SI — source issue — регистр источника, используется для хранения смещения Источника при выполнении строковых команд);

DI - 16 бит, общего назначения, используется в операциях с памятью (DI - destination issue — регистр приемника, используется для хранения смещения пункта назначения при выполнении строковых команд).


^ Сегментные регистры. Данные регистры используются как указатели на сегменты. Сегмент - это блок размером в 64К, который предназначен для определенных целей: для хранения программного кода, данных и т. д.

DS - сегмент данных;

CS - сегмент кода;

ES - дополнительный сегмент;

SS - сегмент стека;

IP - счетчик.

Флаговый регистр

Этот регистр сохраняет статусы состояния процессора, такие как: Z (zero - ноль), С (carry - перенос) и т. д. Этот регистр не доступен напрямую, но его содержимое можно узнать с помощью соответствующих инструкций.

Общий вид ассемблерной функции

Процедуры ассемблера очень похожи на Си-функции. У них есть начало, конец и код, расположенный в середине. В этой книге мы будем ссылаться на Microsoft macro assembler версий от 5.1 до 6.1 (MASM). Это связано с тем, что.в них уже есть директивы, упрощающие стыковку с программами на Си.


Примечание

MASM версии 5.0 подойдет для первого примера, но для успешной работы с книгой вам понадобится версия 5.1 или старше.


Прежде чем начать изучение способов передачи параметров и прочих интересных вещей, давайте посмотрим, что нам нужно как минимум для написания внешней программы на ассемблере, которую мы потом вызовем из Си. Итак, Листинг 2.1 покажет вам прототип процедуры на ассемблере.

Листинг 2.1. Прототип процедуры для MASM 5.0 и более старших версий.

.MODEL MEDIUM ;тип модели памяти

.CODE

; начало кода

PUBLIC _function_name ;информация для компоновщика. Функция

;может экспортироваться

_function_name PROC FAR ;название и тип функции(ближняя

;или дальняя). Ближние функции можно использовать для моделей

;памяти SMALL и COMPACT, а дальние применяются для моделей

;памяти MEDIUM, LARGE и HUGE.

push ВР ;готовим фрейм стека–пролог

;функции

mov BP,SP ;сохраним стек

;Работа функции

pop ВР ;восстанавливаем фрейм стека

;эпилог функции

_function_name ENDP ; конец процедуры

END

;конец кода

Давайте проанализируем программу, приведенную в Листинге 2.1.

Первая директива, которую мы встречаем — это .MODEL. Как и компилятор Си, MASM должен знать, какая из моделей памяти используется. Ключевое слово MEDIUM означает, что мы собираемся использовать модель памяти именно типа MEDIUM. Теперь я хочу напомнить вам свойства основных моделей памяти:

Модель SMALL имеет один 64-килобайтный сегмент для кода и один сегмент для данных;

Модель COMPACT имеет один 64-килобайтный сегмент для кода и несколько сегментов данных;

Модель MEDIUM имеет один 64-килобайтный сегмент для данных и несколько сегментов для кода;

Модель LARGE имеет несколько сегментов как для кода, так и для данных;

Модель HUGE разрешает данным быть больше, чем 64К,но в остальном полностью похожа на модель LARGE.



Чаще всего мы будем использовать модели памяти MEDIUM и LARGE.

Следующая директива — PUBLIC. Она говорит MASM, что следующее имя будет экспортировано, то есть станет «видимо» из других модулей;

Теперь мы переходим к началу самой функции. В ассемблере функция начинается с директивы PROC, которая следует сразу за именем функции;

В этом месте мы находимся внутри исполняемой части кода. Первые две инструкции устанавливают стек таким образом, что процедура получает доступ к параметрам, передаваемым через стек. К этому мы еще не раз вернемся;

В конце процедуры мы очищаем стек;

В конце каждой процедуры ставится ключевое слово ENDP;

В одном блоке мы можем иметь сколько угодно процедур, но надо помнить, что самой последней должна быть директива END. Она сообщает ассемблеру об окончании программы.

Тема следующего разговора - это передача параметров. Наши ассемблерные функции не были бы так полезны, если б мы не имели возможности обмениваться с ними информацией,

Передача параметров

Языки Си и ассемблер похожи на дальних родственников, живущих в одном доме - они вынуждены придерживаться сложных взаимных условностей. Однако ассемблер значительно более примитивен. Поэтому при передаче параметров ассемблерной процедуре нам приходится сочинять множество дополнительных строк кода, обеспечивающих доступ к ним. Вначале необходимо оформить фрейм стека, как показано в Листинге 2.1. Далее необходимо получить доступ к переданным параметрам, основываясь на новом значении регистра базы (ВР). Для обеспечения доступа к параметрам вы должны четко представлять себе, как именно передаваемые параметры размещаются в стеке. К примеру, вы хотите написать процедуру, вычисляющую сумму двух чисел и возвращающую результат в регистре АХ. На языке Си, описание этой функции выглядит так:

int Add_Int(int number_1, int number_2);

При выполнении этой процедуры компилятор языка Си создаст фрейм стека и поместит туда параметры. Иными словами, значения number_1 и number_2 будут расположены в стеке. Вы можете подумать, что сначала в стек будет помещено значение number 1, а затем - number_2. Однако компилятор Си думает несколько иначе. Он помещает параметры в стек в обратном порядке, что облегчает доступ к ним. За счет применения обратного порядка размещения параметров, адрес каждого из них будет задаваться некоторым положительным смещением относительно регистра ВР, что делает жизнь намного легче. В частности, именно благодаря такому механизму, некоторые функции (например, printf) могут получать переменное число параметров. Таким образом, при вызове функции Add_Int фрейм стека будет выглядеть, как показано па рисунке 2.1 или 2.2, в зависимости от используемой модели памяти. Причина, по которой вид фрейма стека зависит от модели памяти, состоит в следующем: при вызове процедуры в стек помещается адрес команды, следующей непосредственно за командой вызова. Если мы применили модель памяти SMALL, все процедуры по определению находятся внутри одного кодового сегмента. Следовательно, для доступа из программы к любой из них нам необходимо знать только смещение. Как известно, значение смещения занимает два байта. Если же мы применяем модель памяти MEDIUM или LARGE, то должны сохранить как смещение, так и сегментную часть адреса. Вместе сегмент и смещение занимают уже целых четыре байта.

Как видно из рисунков 2.1 и 2.2, параметры помещаются в стек в том порядке, который обеспечивает их адресацию положительными смещениями относительно значения регистра базы (ВР). Следовательно, для доступа к параметру number 1 вы должны использовать [ВР+4] или [ВР+6], в зависимости от установленной модели памяти. В качестве примера рассмотрим полный текст функции Add_Int. Она вычисляет сумму двух передаваемых в качестве аргументов чисел. Результат возвращается в регистре АХ, который, в соответствии с соглашениями языка Си, используется для возврата 16-битных значений.

Листинг 2.2. Простая процедура сложения.

; Секция констант

integer_1 EQU [ВР+6] ; задает адрес первого аргумента

integer_2 EQU [BP+8] ; задает адрес второго аргумента

.MODEL medium ; указываем компилятору, что он должен

; использовать модель памяти MEDIUM

.CODE ; начало кодового сегмента

PUBLIC _Add_Int ; эта функция - общедоступна

_Add_Int PROC FAR ; имя функции и ее тип (дальняя)

push BP ; эти две инструкции инициализируют

; фрейм стека

mov ВР, SP

mov AX,integer_1 ; помещаем первое слагаемое

; в аккумулятор (регистр АХ)

add AX,integer_2 ; добавляем второе, слагаемое

; к содержимому АХ

pop ВР ; ликвидируем фрейм стека

_Add_Int ENDP ; конец процедуры

END ; конец кодового сегмента

Единственное, что мы изменили по сравнению с Листингом 2.1, это добавили несколько строк кода и ввели определения для адресов параметров. Теперь давайте проанализируем то, что у нас получилось.

Как и в предыдущем листинге, здесь были использованы директивы ассемблера для указания модели памяти, способа вызова, начала и конца функции;

EQU — это простая директива, заменяющая одну строку на другую. Я прибег к ней потому, что мне не хотелось в тексте самой функций использовать синтаксические конструкции [ВР+6] и [BP+8]. Строки, задающие выражения, которые будут подставлены при компиляции, это:

integer_l EQU [ВР+6]

integer_2 EQU [BP+8]

В общем, использование таких подстановок позволяет сделать ассемблерную программу более читабельной. Единственной альтернативой такому подходу является написание команды индексирования относительно содержимого одного из регистров (типа [ВР+6]).

Директива USES

Надо сказать, что ассемблер MASM, начиная с версии 5.1, имеет некоторые новые директивы, упрощающие порядок передачи параметров и создания фрейма стека. Для этого вы можете использовать директиву USES вместе с директивой PROC. Они сообщат ассемблеру, какие именно регистры будут использоваться в функции. Директива USES оберегает вас от всей рутины, связанной с определением стекового фрейма и подстановками переменных. Более того, она генерирует код пролога и эпилога для сохранения регистров, которые вы указали для использования в функциях. Таким образом, содержимое этих регистров не будет изменено, когда процедура вернет управление вызвавшей ее Си-функции.

Внимание!

Помните, что Си и ассемблер используют одни и те же регистры процессора. Если вы пользуетесь регистром в ассемблерной программе, то должны его сохранить в стеке и восстановить перед завершением функции. Иначе, ваша Си-программа может просто "сломаться" в момент выхода из вызова ассемблерной вставки.

Директива PROC и относящийся к ней уточнитель USES имеет следующий синтаксис.

label PROC [[attributes]] [[USES register_list]] [[,]]

[[parameter list][:type]]...]]

Поле label — это имя процедуры;

Поле attributes сообщает ассемблеру свойства вашей процедуры. Она может содержать множество параметров, таких как тип процедуры (NEAR или FAR), «видимость» процедуры (PUBLIC или PRIVATE) и, наконец, тип языка (С, PASCAL и т. д.). Эта возможность делает наши программы на ассемблере более читаемыми. Правда, это связывает руки, но зато программы обретают определенную элегантность;

Поле register_list показывает, какие регистры будет использовать функция. При этом ассемблер генерирует код, который может сохранить их на время работы процедуры и восстановить при выходе;

Поле parameter_list очень похоже на список параметров в Си;

Для каждой передаваемой процедуре переменной должен быть указан тип, определяющий их размер (например, BYTE или WORD). Тип задается в поле type.

Если вы пишите процедуру, в которую передаете три целых величины, и будете использовать регистры SI, DI и СХ, то должны включить следующий оператор:

far proc USES SI DI СХ, integer_1:WORD, integer_2:WORD,

integer_3:WORD

Используя директивы PROC и USES, давайте перепишем процедуру из Листинга 2.2.

Листинг 2.3. Модифицированная версия Add_Int.

.MODEL MEDIUM,С ; использовать модель MEDIUM ; и соглашения по вызову Си

.CODE ; начало кода


PUBLIC _Add_Int ; объявляем функцию как общедоступную

_Add_lnt PROC USES integer_1 :WORD, integer_2 :WORD

mov AX,integer_l ; загрузить первый операнд в AХ

add AX,integer_2 ; сложить второй операнд с AХ

_Add_Int ENDP ; конец процедуры

END ; конец кода

Как видно из Листинга 2.3, тяжкое бремя сохранения регистра ВР, создания и уничтожения стекового фрейма теперь отдано на откуп ассемблеру. Более того мы получили прямой доступ к параметрам integer 1 и integer 2.

Передача указателей

Мы знаем, как передать значения таких параметров как BYTE или WORD, но как передать указатель? Указатели передаются как двойные слова, или DWORD. Для доступа к указателям в стеке нам придется воспользоваться старым приемом: разобьем двойное слово указателя на две переменные segment и offset, которые будут иметь тип WORD, и уже к ним будем обращаться в. нашей ассемблерной программе. К примеру, если мы вызываем ассемблерную функцию в модели MEDIUM, (скажем, это будет вызов типа FAR) в следующей строке:

pfoo(&x)

то получить адрес переменной Х можно будет с помощью следующих подстановок:

offset EQU [ВР+6] segment EQU [BP+8]

Если мы захотим изменить значение X, то нам придется сделать следующее:

mov DI,offset

mov AX,segment

mov ES,AX

mov ES:[DI],CX

Эта программа состоит из двух основных частей:

Во-первых, создается указатель на Х через регистры ES и DI;

Во-вторых, изменяется значение переменной X.

Ну вот и все о том, что связано с передачей параметров. Новые расширения директив PROC и USES просто великолепны, и вы можете всегда ими пользоваться, если чувствуете от их применения комфорт. Если вы предпочитаете все делать в стиле MASM 5.0, то это ваше право. С точки зрения быстродействия программы здесь нет никакой разницы.

Локальные переменные

Теперь вы знаете, как передавать переменные в процедуры, а вот как насчет временных и локальных переменных, которые действуют только внутри процедуры?

Когда мы пишем программы на Си, то применяем локальные переменные для вычислений, сохранения результатов и т. д. Так же, как и в Си, мы можем создать локальные переменные в наших ассемблерных функциях, используя стек. Конечно, можно создать локальные переменные в области данных (например, в сегменте данных), но этого не стоит делать. Почему? Дело в том, что стек как раз и создавался для временного хранения данных и локальных переменных, так что именно им и стоит пользоваться.

Давайте посмотрим, как это делается. Когда мы определяем стек, сохраняя регистр ВР, то можем извлекать параметры путем прибавления положительного смещения к регистру ВР, например, [ВР+6] и т. д. Таким образом, получается, что в действительности стек — это область непрерывной памяти, которую мы можем использовать также и для хранения локальных переменных. Для этого нам надо только использовать отрицательное смещение относительно регистра ВР.

В случае, если мы хотим иметь две локальные переменные 1осаl_1 и 1оса1_2, можно использовать следующую подстановку:

local_1 EQU [ВР-2]

local_2 EQU [BP-4]

Это дает нам два целых числа. Пока нам не известно, что записано по этим адресам и мы можем только предполагать, что данный участок памяти можно использовать безболезненно. Однако нужно помнить, что мы только что использовали стек для хранения данных и теперь нам необходимо самим изменить регистр SP для отражения этого. Если этого не сделать, то первая встретившаяся инструкция PUSH обязательно что-нибудь запишет в нашу переменную и непременно «испортит» ее.

Для исключения этой ситуации можно предложить уменьшить значение регистра SP на длину переменных, которые мы хотим сохранить. В нашем случае это составит четыре байта. В Листинге 2.4 показано, как это сделать.


Листинг 2.4. Корректировка регистра SP.

push ВР ; устанавливаем фрейм стека, как обычно

mov BP,SP

sub SP,4 ; корректируем указатель стека. Теперь наши

;переменные не будут случайно изменены

;Вся работа выполняется здесь

add SP,4 ; перед уничтожением фрейма стека надо восстановить

; исходное значение указателя стека

pop ВР

; уничтожаем фрейм стека,

; восстанавливая регистр ВР

Директива LOCAL

Заметьте, что в Листинге 2.4 мы изменили значение регистра SP не только в начале процедуры, но и в конце (перед тем как восстановить регистр ВР). Эта техника обычно используется для размещения переменных в ассемблерных процедурах при их вызовах из языков высокого уровня.

В листинге 2.4 это делалось вручную. А вот MASM 5.1 и более поздние версии имеют встроенную директиву, которая выполняет это автоматически. Это директива LOCAL и она имеет следующий синтаксис:

LOCAL variable name: type, variable name: type, ...

(Любопытно. MASM все больше становится похож на Си. К чему бы это?) Давайте теперь напишем программу с использованием директивы LOCAL. Она называется Timer и требует одного параметра — time, который затем помещает в локальную переменную asm time. Из Си этот вызов будет выглядеть так:

Timer(25);

Листинг 2.5 показывает реализацию программы Timer на ассемблере, используя все директивы, которые мы обсудили в этой главе.

Листинг 2.5. Программа Timer.

.MODEL MEDIUM ;используем модель MEDIUM

.CODE ;начало кодового сегмента

;в процессе работы функция меняет содержимое регистра АХ

_Timer PROC FAR USES AX, time:WORD LOCAL asmt_time :WORD

mov AX, time

mov asm_time, AX

_Timer ENDP END

еще рефераты
Еще работы по разное