Текст
                    У Морер
язык
АССЕМБЛЕРА
для персонального
компьютера
эпл
Издательство «Мир»

Язык ассемблера для персонального компьютера Эпл
Apple Assembly Language a course of study based on LazerWare software W. Douglas Maurer The George Washington University Computer Science Press 1984
У Морер ЯЗЫК АССЕМБЛЕРА для персонального компьютера ЭПЛ Перевод с английского канд. техн, наук И. П. ПЧЕЛИНЦЕВА, канд. пед. наук К. Г. ФИНОГЕНОВА МОСКВА «МИР» 1987
ББК 22.18 М 79 УДК 681.3 Морер У. М 79 Язык ассемблера для персонального компьютера Эпл: Пер. с англ. — М.: Мир, 1987. —430 с. В книге американского специалиста описывается язык ассемблера персо- нальной ЭВМ Эпл, построенной на базе микропроцессора 6502. Рассматривается система команд ЭВМ, способы адресации, работа с 8-разрядными и 16-разряд- ными числами. Значительное внимание уделяется технике составления и опти- мизации программ на языке ассемблера, а также их отладки. Для преподавателей средних школ, техникумов, студентов вузов, специалис- тов различных отраслей народного хозяйства. 2405000000-277 М 041(01)—87 170—87, ч. 1 ББК 22.18 Редакция литературы по информатике и робототехнике @ 1984 Computer Science Press, Inc, © перевод на русский язык, «Мир*, 1987
Предисловие переводчика Предлагаемая читателю книга представляет собой добротно написанное учебное пособие по языку ассемблера персональной микроЭВМ Эпл. Поскольку основой этой машины является мик- ропроцессор §502, с таким же успехом можно сказать, что в книге описывается язык ассемблера этого микропроцессора. МикроЭВМ Эпл (Эпл II, Эпл II+, ЭплПе, Эпл III), выпускае- мые корпорацией Apple Computer, относятся к числу наиболее популярных и распространенных во всем мире персональных ЭВМ. Оперативная память достаточно большой емкости (48К или 64К байт), встроенное системное программное обеспечение на ПЗУ (монитор и интерпретатор Бейсик), дисплей для выво- да алфавитно-цифровой и графической информации, наличие дисковой памяти и дисковой операционной системы DOS, воз- можность использования языков программирования высокого уровня (Паскаль, Кобол) и другие характерные для современ- ных персональных ЭВМ черты делают эту машину чрезвычай- но удобной для проведения инженерно-технических расчетов, анализа экспериментальных данных, построения персональных обучающих систем. Конструкцией ЭВМ предусмотрена воз- можность подключения интерфейсных плат для управления дополнительными периферийными устройствами, в том числе нестандартным электронным оборудованием, а также исполь- зования ПЗУ или ППЗУ небольшой емкости для хранения управляющих программ. Концепция персональных ЭВМ предполагает, в частности, широкое использование готового программного обеспечения (текстовые процессоры, программы планирования бюджета, средства создания персональных баз данных, программы меж- машинной связи по телефонным линиям и обращения к инфор- мационным системам коллективного пользования и т. д.), что позволяет во многих случаях обойтись без программирования. Для решения же вычислительных задач и проведения научно- технических расчетов, а также для управления аппаратно-про- граммными средствами ЭВМ (графические системы, звуковой генератор, порты ввода-вывода и т. д.) используется язык Бейсик. Основные недостатки этого языка—низкая скорость выполнения написанных на нем программ и ограниченные воз- можности по созданию программ управления нестандартным оборудованием. Существенно большие возможности предостав- ляет язык ассемблера, широко используемый при разработке
6 Предисловие переводчика программного обеспечения измерительно-вычислительных комп- лексов и других систем реального времени. Книга У. Морера является одним из наиболее полных и удачных в методическом отношении пособий по изучению языка ассемблера процессора 6502. Начиная с простейших представ- лений об организации ЭВМ и хранении в ней двоичной инфор- мации, автор последовательно описывает средства языка ас- семблера, подробно останавливаясь на их использовании при выполнении арифметических и логических операций, организа- ции циклов и переходов, операциях с массивами, обработке символьной информации и т. д. Процессор 6502 имеет 8-битовую разрядность, что серьезно ограничивает его вычислительные возможности. В книге боль- шое внимание уделяется приемам работы с 16-разрядными (2-байтовыми) операндами, использование которых требует спе- циальной техники программирования. При организации на базе микроЭВМ измерительных систем реального времени особую важность приобретает оптимизация программы по памяти и времени .выполнения. Автор уделяет должное внимание данной проблеме. Кроме специальной главы, посвященной этому вопросу, в книгу включено большое коли- чество примеров и задач на оптимизацию программ. Известно, что отладка и корректировка программ на языке ассемблера весьма трудоемки и требуют специальных приемов и системных средств. Большой раздел книги, посвященный это- му не слишком освещенному в литературе вопросу, содержит много полезных рекомендаций, ценность которых выходит за рамки конкретного языка, изучаемого в книге. Следует заметить, что для процессора 6502 разработано не- сколько ассемблеров — от мини-ассемблера, обладающего мини- мальными возможностями, до мощного ассемблера TLA, под- держивающего аппарат макрокоманд и условного ассемблиро- вания. Ассемблер LISA, описанный в книге, несмотря на более скромные возможности, является, пожалуй, наиболее распро- страненным. Книгу отличает ясность и последовательность изложения. Большое количество упражнений, сопровождаемых ответами и пояснениями, помогут читателю глубже разобраться .в рассмат- риваемых вопросах. Книга может быть рекомендована инже- нерно-техническим работникам любых специальностей, желаю- щим освоить программирование микропроцессора 6502 или его аналогов, а также студентам, аспирантам и преподавателям вузов. Перевод книги выполнен И. П. Пчелинцевым (статьи 67— 77) и К. Г. Финогеновым (остальные). К. Г. Финогенов
Предисловие Книга является пособием для студентов, изучающих второй язык программирования, после того как ими освоен Бейсик (или, возможно, Паскаль, Фортран или ПЛ/1). Она может быть использована в университетах, колледжах, школах, а также для самостоятельного изучения. Автор пользуется ею в Универ- ситете Джорджа Вашингтона, на факультете электротехники и вычислительной техники. Бейсик, так же как и другие алгебраические языки, например Фортран и Паскаль, в значительной степени машинно-незави- сим. Вы можете написать программу на Бейсике для одной ЭВМ и ожидать, что для ее выполнения на другой ЭВМ вам придется внести в программу лишь минимальные изменения. В этой книге, однако, вы познакомитесь с языком ассемблера и машинным языком, которые оказываются совершенно раз- личными для разных семейств ЭВМ. Учитывая это обстоятельство, нам прежде всего надо ре- шить, на какую ЭВМ ориентироваться. В этой книге предпо- лагается, что вы работаете на ЭВМ Эпл, которая является одной из самых популярных ЭВМ в мире. (Говоря «Эпл», мы всегда будем понимать Эпл II+, Эпл Пе, либо Эпл III, рабо- тающую в режиме эмуляции). Эпл представляет собой вычис- лительную систему, собственно вычислительной частью которой служит процессор 65021). Таким образом, в этой книге рас- сматривается язык ассемблера и машинный язык процессора 6502. Для практического освоения этих языков вам понадобится ассемблер. Это специальная программа, обрабатывающая про- граммы на языке ассемблера примерно так же, как различные системы Бейсик, используемые на ЭВМ Эпл, обрабатывают программы на языке Бейсик. Мы будем предполагать, что у вас есть какой-либо из ассемблеров LISA 1.5, LISA 2.5 48К или LISA 2.5 64К2)> * 2 3). В целом ряде обзоров признавалось, что ас- Процессор 6502, в силу технологических особенностей его выполнения называется микропроцессором. Он изготавливается фирмой MOS Technology, Inc. в г. Морристауне, шт. Пенсильвания. ЭВМ Эпл выпускается фирмой Apple Computer Corporation в г. Купертино, шт. Калифорния. 2) Все они разработаны фирмой Laser Ware. Заметьте, что ассемблер LISA ие имеет ничего общего с ЭВМ LISA, выпускаемой корпорацией, Apple Computer. 3) LISA представляет собой ассемблер (т. е. системную программу, пред- назначенную для преобразования исходного текста программы пользователя на языке ассемблера в машинные коды), оснащенный средствами создания и редактирования исходного текста, выгрузки его на диск и сохранения там в в виде файла и т. д. (см. табл. П13). — Прим, перев.
8 Предисловие семблеры LISA являются лучшими ассемблерами для ЭВМ Эпл, и оба продукта — и сами ЭВМ Эпл, и ассемблеры LISA — можно приобрести в любом магазине, торгующем вычислитель- ной техникой. Если у вас другая версия ассемблера LISA или вообще другой ассемблер для Эпл, вы можете использовать его, изучая книгу, но одновременно держать под рукой справочник по этому языку, чтобы отмечать те места книги, которые спе- цифичны для описанных в ней версий ассемблера. Вы даже можете пользоваться этой книгой, работая на ЭВМ Атари 800 или VIC-20, однако в этом случае ассемблер, техника отладки программ и основные системные подпрограммы будут иными. Для того чтобы использовать ассемблер LISA, вы должны, как правило, иметь на ЭВМ по крайней мере 48К памяти и запоминающее устройство на магнитных дисках. Дисковое устройство стоит несколько сотен долларов, но весьма полезно при работе на Бейсике и на языке ассемблера. Не имея диско- вого устройства, вы будете вынуждены пользоваться запомина- ющим устройством на магнитной ленте, которое не так надеж- но и работает медленнее, чем дисковое. Если на вашей ЭВМ меньше 48К памяти, вам полезно приобрести дополнительную память, которая стоит еще меньше и весьма полезна (хотя и не необходима) при работе с Бейсиком; в этом случае вы смо- жете выполнять более длинные программы и обрабатывать мас- сивы большей длины. Машинный язык — это основной язык ЭВМ. Любая ЭВМ не- посредственно понимает только один язык, именно машинный язык. Имеется лишь два способа заставить ЭВМ воспринимать язык, отличающийся От машинного. Первый заключается в трансляции (переводе) программы, написанной на каком-то другом языке, на машинный язык и затем в выполнении полу- ченной программы на машинном языке. Трансляция выполняет- ся специальной программой, которую обычно называют компи- лятором. Компиляторы разработаны для Бейсика, Фортрана, Паскаля и многих других языков. Другой способ — использовать программу, которая транслирует одно предложение языка; за- тем выполняет это предложение; затем транслирует и выполня- ет следующее предложение и т. д. Такая программа называется интерпретатором. Большинство систем Бейсик на ЭВМ Эпл яв- ляются интерпретаторами (хотя для Эпл разработаны и ком- пиляторы с Бейсика). Машинный язык состоит исключительно из двоичных чисел,- о которых также будет идти речь в этой книге. Например, на машинном языке процессора 6502 аналогом оператора Бейсика GOSUB является двоичное число 00100000. Точно так же дво- ичное число 0000100010101100 может соответствовать перемен ной J в какой-то конкретной программе.
Предисловие 9 Машинный язык весьма неудобен для составления на нем программ, и для облегчения работы был придуман язык ас- семблера. Этот язык очень похож на машинный, только вместо чисел в нем используются имена. Так, аналогом GOSUB на язы- ке ассемблера для процессора 6502 является JSR, что представ- ляет собой сокращение от Jump to a Sub-Routine, переход на подпрограмму, точно так же как GOSUB обозначает Go to а SUBroutine, перейти к подпрограмме. Соответствие предложений языка ассемблера и машинного языка избавляет нас от необходимости составлять большие программы непосредственно на машинном языке. Однако много больших и хорошо работающих программ были написаны и пишутся в настоящее время на языке ассемблера. К ним отно- сится, например, программа монитора Эпл—важнейшая про- грамма, которая управляет машиной и позволяет ей восприни- мать и выполнять программы на машинном языке. Для успешного изучения вычислительной техники абсолютно необходимо знание и языка ассемблера, и машинного языка. Это справедливо, даже если в дальнейшем большинство про- грамм, которые вам придется разрабатывать, будут написаны на каких-то других языках, таких, как Паскаль или Си. Нам представляется важным, чтобы студент практически умел ра- ботать на одном из языков ассемблера, а не имел бы лишь общее представление о них. Некоторые специалисты по вычислительной технике недо- оценивают важность языка ассемблера. Действительно, в прош- лом многие большие программы необоснованно писались на языке ассемблера, что приводило к чрезмерным затратам на отладку. Однако именно на языке ассемблера составлены весь- ма удачные программы; к ним относятся монитор Эпл, система СР/М и VISICALC1). Обычно программы, для которых сущест- венна высокая скорость работы и малое потребление памяти, в частности операционные системы и интерпретаторы, пишутся на языке ассемблера, что позволяет удовлетворить указанным тре- бованиям. Многие специалисты предпочитают сначала обучать машин- ному языку, и лишь затем — языку ассемблера. В этой книге мы начинаем с языка ассемблера, так как полагаем, что именно он потребуется студентам в процессе обучения. Знание машин- ного языка необходимо для проведения отладочных работ, но мы не думаем, что студенты будут программировать на нем непосредственно (как это случается, например, при изучении СР/М — широко распространенная операционная система для мик- роЭВМ на базе микропроцессоров 8080, 8085 и Z80; VISICALC — популярная программа планирования бюджета, предназначенная для использования на персональных микроЭВМ. — Прим, пере в.
10 Предисловие курса программирования, ориентированного на логическое про- ектирование аппаратных средств вычислительной техники). При чтении этой книги вам понадобится умение програм- мировать на языке Бейсик1), умение, которым, по-видимому, вы уже обладаете. Если вы не знаете Бейсика, но изучали Фортран, Паскаль или ПЛ/1, вы можете воспользоваться таблицей П1, в которой проведено сопоставление предложений Бейсика, ис- пользуемых в этой книге, |С соответствующими операторами указанных языков. Изложение материала в книге во многих случаях опирается на сравнение Бейсика и языка ассемблера; если вы знаете Бейсик, но не слишком хорошо, вам придется подновлять ваши знания по мере изучения книги. Для машинного языка и языка ассемблера характерно го- раздо большее число типов предложений, чем для Бейсика. Процессор 6502 предусматривает 56 типов предложений, причем это еще немного — системы команд некоторых ЭВМ насчитыва- ют их в три с лишним раза больше. Использование Бейсика для объяснения языка ассемблера и машинного языка преследует цель провести студента через эти сложности с максимальной эффективностью. Книга разделена на 100 статей, каждая из которых содер- жит по 3 упражнения. Для односеместрового 14-недельного кур- са рекомендуется проработка 7 статей в неделю; при полусе- местровом 10-недельном курсе вы можете прорабатывать пс 8 статей в неделю и остановиться на статье 76 (остальная часть книги посвящена прогрессивным идеям и методам вычислитель- ной техники). Материал статей 82 (обработка строк), 83—89 (сортировка и поиск), а также 97—99 (вычисления с плаваю- щей точкой) при необходимости может быть проработан раньше. Хотелось бы подчеркнуть, что при составлении было обращено самое пристальное внимание на такое расположение материала, при котором никакие понятия не используются до тех пор, пока они не объяснены. Ввиду того что даже простейшие задачи требуют использо- вания большого числа разнообразных предложений, мы не ре- комендуем, чтобы студент «начал выполнять программы на ЭВМ уже в самом начале обучения. Вместо этого в процессе обучения используются упражнения с карандашом и бумагой, которые составляют непрерывный поток — по три упражнения в каждой статье — и охватывают все основные понятия, необходимые про- граммисту на языке ассемблера. Статьи с 42 по 50 посвящены процессу создания законченной программы, ручной проверке и прогонке, редактированию, ас- семблированию, а также нескольким методам отладки, включая и Конечно, не обязательно Эпл-Бейсик.
Предисловие 11 пошаговое выполнение, отладку с точками останова и коррек- цию программы с помощью заплат на языке ассемблера. Все это рассмотрено применительно к ЭВМ Эпл, ее монитору и си- стеме LISA. После статьи 50, а затем после каждой десятой статьи предлагается задача для 'решения на ЭВМ. Нельзя пе- реоценить важность (выполнения этих или схожих задач на ЭВМ Эпл, особенно для тех, кто изучает книгу самостоятельно. Книжного знакомства с программированием на языке ассембле- ра и машинном языке недостаточно! Для понимания книги не требуется знать устройство ЭВМ, и в самой книге очень незначительная часть материала затра- гивает аппаратные средства. После того как студент познако- мится с основами программирования на языке ассемблера и машинном языке по этой книге, ему рекомендуется прослушать отдельный курс аппаратных средств микроЭВМ, интерфейсов и программного обеспечения логического проектирования. С дру- гой стороны, знание языка ассемблера и машинного языка необходимо в таких областях, как разработка ассемблеров и компиляторов, где знакомство с аппаратными средствами обьпь но не требуется. В приложение вынесено большое количество таблиц, опи- сывающих процессор 6502, систему LISA и монитор Эпл. Сред- ства ассемблера LISA, описанные в приложении, имеются во всех версиях этого языка. Приведены все команды процессора 6502, но некоторые средства системы LISA и ряд средств мо- нитора Эпл опущены. После того как вы дойдете примерно до середины книги, вы сможете самостоятельно читать литературу, посвященную ЭВМ Эпл. В книге обсуждаются методы умножения и деления (в том числе подпрограммы умножения и деления), а также операции с плавающей точкой, хотя в процессоре 6502 не предусмотрены команды умножения и деления или какие-либо команды для операций с плавающей точкой. Далее, значительное число ста- тей посвящено 16-битовым операциям, хотя процессор 6502 представляет собой 8-разрядную машину. Очевидно, что гораздо проще «нахвататься» знаний, изучая машинный язык 16-разряд- ной машины, после того как вы освоили его на 8-разрядной, чем наоборот, поскольку часто действие, выполняемое на 16-разряд- ной ЭВМ одной командой, может потребовать нескольких ко- манд, если не целой подпрограммы, на 8-разрядной машине. Эта книга не была бы написана, не будь в распоряжении автора двух прекрасных пособий, посвященных процессору 6502, Это, во-первых, книга [29], написанная моим другом и бывшим студентом Родни Заксом; во-вторых, книга [12] Ланса Левента- ля, содержащая около 600 страниц и свыше 150 ссылок. Сле-
12 Предисловие дует также упомянуть энциклопедическую работу [19] Адама Осборна, которая должна стоять на книжной полке любого специалиста по вычислительной технике; Apple II Reference Manual (справочное пособие по ЭВМ Эпл II) и LISA (справоч- ник); обе последние книги рекомендуются как дополнительный справочный материал. Много людей помогли мне в подготовке этой книги. Нед Родс, Джозе Санчес, Билл Шультейс и Ричард Юнайтид прочли книгу от корки до корки и предложили бесчисленное количество поправок и улучшений. В дополнение к этому автор испытывал книгу на аудиторных занятиях в течение четырех последова- тельных семестров; все задачи распределялись среди студентов, которым предлагалось найти в них ошибки. В этом отношении особенно помогли следующие студенты: Том Арден, Манджит Бакши, Бернард Бенсон, Гарри Боу, Джанет Брюс, Винод Кумарасвами, Сюзан Эйзен, Набил Исфахани, Мария Гиа, Ри- чард Гони, Энн Гриди, Филип Гроув, Даниэль Хьютон, Хинда Када, Маргарита Перец Кирк, Рафи Кригман, Лэм Хон Во, Мэри Лу Миллер, Янг-Сил Мантин, Клод Ногей, Джефри Пи- фер, Сарабжит Сингх, Лина Стил, Парафонг Супипат, Абдула- зиз Тамани, Фархад Верахрами, Кати Уайтфилд. Особую благодарность надо выразить Барбаре Фридман и всем сотрудникам издательства Computer Science Press, в том числе Арту Фридману, Сэнди Калу, Элизабет Мергнер, Бесс Швин, Кэрол Уоллас и Рэчел Уитти, за их непоколебимую под- держку и ободрение. Наконец, я хотел бы поблагодарить про- фессора Рэя Рикхольца из Университета Джорджа Вашингтона, моего верного друга и единомышленника. У. Д. Морер Вашингтон, 1982
Статья 1 Коды Эта книга научит вас программировать на машинном языке и на языке ассемблера. Это значительно сложнее, чем програм- мировать на Бейсике или Фортране, но зато вы сможете созда- вать программы, которые будут выполняться в несколько де- сятков раз быстрее, чем типичная программа, написанная на Бейсике. Кроме того, вы поймете, как работает ЭВМ (с точки зрения программиста1)). Это окажет вам неоценимую помощь в дальнейшем изучении вычислительной техники. Почти все, что нам предстоит узнать, изучая программирова- ние на машинном языке или на языке ассемблера, основано на понятии кода. Есть старая детская игра, в которой коды используются для передачи секретных сообщений. Предположим, ваша мать за- ставляет вас брать уроки музыки, в то время как вам хотелось бы заниматься совсем другим. Вы хотите послать секретное со- общение PIANO LESSONS STINK2) Теперь «отсчитайте вперед» по алфавиту от каждой из этих букв, например, по три’позиции: PIANO LESSONS STINK QJBOP METTPOT TUJOL RKCPQ NGUUQPU UVKPM SLDQR OHWRQV VWLQN В каждом столбце, отсчитывая по три позиции, мы получим последовательно буквы Р, Q, R, S; I, J, К, L и т. д. Если теперь вы пишете записку приятелю, в которой сказано SLDQR OHWRQV VWLQN то, даже если она попадет в руки вашей матери или учителю музыки, они не смогут догадаться, что в ней написано (впрочем, если они сами не играли в эту игру, когда были маленькими). *> Но не с точки зрения разработчика аппаратной части ЭВМ. Вопросы логического и аппаратного проектирования ЭВМ здесь не рассматриваются. Превосходное описание устройства процессора 6502 содержится в [12]. г> Уроки музыки отвратительны. — Прим, перев.
14 Статья 1 Конечно, ваш приятель может ответить вам запиской HVSHFLDOOB VFDOHV Желая узнать, что вам пишет приятель, вы должны отсчитывать назад по алфавиту: HVSHFLDOOB VFDOHV GURGEKCNNA UECNGU FTQFDJBMMZ TDBMFT ESPECIALLY SCALES1» Обратите внимание на букву Y в слове ESPECIALLY. Отсчиты- вая вперед и дойдя до Z, мы снова переходим к А. Аналогично, отсчитывая назад и дойдя до А, мы снова переходим к Z. Можно воспользоваться другим кодом — числовым, в кото- ром 1 обозначает А, поскольку А есть первая буква алфавита. Если в вашей записке приятелю сказано 16 9 1 14 15 12 5 19 19 15 14 19 19 20 9 14 11, то это опять-таки обозначает PIANO LESSONS STINK, потому что Р — 16-я буква алфавита, I — 9-я и т. д. Возможно, впрочем, что ваша мать легко расшифрует этот код; однако именно код такого рода чаще всего используется в машинном языке и в языке ассемблера. Понятие кода является для нас фундаментальным, потому что вся информация в ЭВМ содержится в закодированной фор- ме. Если бы мы захотели записать в память ЭВМ фразу PIANO LESSONS STINK, то каждая буква этой фразы была бы пред- ставлена кодом* 2». Для процессора 6502 код буквы I, например, 11001001. Каждый раз, когда мы передаем информацию ЭВМ, она должна быть закодирована; каждый раз, когда мы извле- каем информацию из ЭВМ, ее надо декодировать. Все это де- лается программами преобразования кодов, составлять которые мы научимся с течением времени. Часто при написании и изу- чении программ нам будет удобнее считать, что информация хранится .в ЭВМ непосредственно в том виде, в каком мы ее представили, однако всегда надо иметь в виду, что в действи- тельности это не так. Машинные коды, как, например, 00010011 или 11001001, представляют собой двоичные числа. В двух следующих стать- ях мы рассмотрим важные для нас элементарные свойства дво- ичных чисел. Особенно гаммы. — Прим, перев. 2> Пробел между словами также представляется кодом. Коды символов будут рассмотрены подробнее в статье 24.
Двоичные числа 15 Упражнения0 1. Расшифруйте следующие сообщения, воспользовавшись описанным выше буквенным кодом: a) ZKDW LV WKH DQVZHU WR SUREOHP ILYH? * б) ILIWHHQ LQFKHV в) GDQQB LV FXWH! 2. Расшифруйте следующие сообщения, воспользовавшись описанным выше числовым кодом: $ а) 13 5 5 20 13 5 2 5 8 9 14 4 20 8 5 6 5 14 3 5 б) 7 5 20 12 15 19 20, 25 15 21 3 18 5 5 16! * в) 23 9 12 12 25 15 21 7 15 15 21 20 23 9 20 8 13 5? 3. Следующее сообщение было закодировано дважды, бук- венным кодом и числовым кодом. Расшифруйте его 12 4 16 12 17 15 18 25 8 26 12 23 11 22 24 22 12 8. Статья 2 Двоичные числа Прежде чем начать изучение процессора 6502, необходимо освоить двоичные числа и двоичную систему счисления. Неко- торые получают эти знания в процессе изучения Бейсика или из курса математики в высшей школе, но мы будем исходить из того, что вы никогда раньше не сталкивались с двоичными чи- слами, и приступим к их изучению с самого начала. Двоичные числа — это числа, состоящие только из единиц и нулей. На рис. 2.1 изображены первые 16 двоичных чисел (вместе с нулем). » Ответы на упражнения, помеченные звездочкой (*) помещены в конце книги.— Прим. ред.
16 Статья 2 Числа (десятичные) Двоичные числа X 2х 0 0 0 1 1 01 1 2 2 10 2 4 3 и 3 8 4 100 4 16 5 101 5 32 6 по 6 64 7 111 7 128 8 1000 8 256 9 1001 9 512 10 1010 10 1024 11 1011 11 2048 12 1100 12 4096 13 1101 13 8192 14 1110 14 16384 15 1111 15 32768 16 10000 16 65536 Рис. 2.1. Первые 16 двоичных чисел. Рис. 2.2. Степени двух. Двоичные числа можно рассматривать как некоторый код вроде кода для секретных сообщений. Так, двоичное число 111 представляет собой код числа 7; 1001 есть код числа 9. Все числа в ЭВМ хранятся в виде таких кодов. Например, число 9 хранится в виде 1001 (или иногда 00001001). Обычные числа, такие, как 7 или 9, называются десятичны- ми числами. Известная нам всем десятичная система позволяет выражать числовые значения с помощью нескольких цифр (раз- рядов). Так, 3456 = 3000+400+50+6= (3+1000) + (4+100) + (5* 10) +6 = = (3+103) + (4+102) + (5+101) + (6*10°), где показатели степени 10 расположены в порядке убывания (.вспомним, что 10’= 10 и 10° = 1). Двоичная система отличается от десятичной тем, что основа- ние 10 заменяется на основание 2. Так, в двоичной системе 1001 = (1*23) + (0*22) + (0*2') + (1*2°) = (1+8) + (0+4) + + (0*2) + (1*1) =8+1=9.
Двоичные числа 17 1 i Нахождение двоичного кода для десятичного числа называ- ется/преобразованием числа из десятичной системы в двоичную. Чтобы выполнить такое преобразование, мы прежде всего долж- ны найти наибольшую степень двух, которая не превышает пре- образуемое число. Возьмем, например, число 1600. Наибольшая степень 2, не превышающая 1600, есть 1024 (см. рис. 2.2). t 1 А Г (256) (128) - (32) (16) (8) (4) ‘ (2) (1) Ответ 1600 — 1024 1 576 -512 1 64 0 64 0 64 -64 1 0 0 0 0 0 0 0 0 0 0 0 -= 11001000000 (512) (128) (64) (8) (4) (2) (1) Ответ 1331 -1024 307 307 -256 51 51 51 -32 19 -16 3 3 3 —2 1 -1 0 = 10100110011 1 0 1 0 0 1 1 0 0 1 1 Рис. 2.3. Преобразование чисел из десятичной формы в двоичную. Рассмотрим теперь каждую степень 2 в последовательности от 1024 до 1. Будем по очереди вычитать их, следя за тем, что- бы результат не получился отрицательным. Каждый раз, когда вычитание степени 2 .возможно, записываем 1 в двоичное число; каждый раз, когда вычитание степени 2 невозможно, записыва- ем 0 (см. рис. 2.3). Закончив преобразование, прочитаем резуль- тат сверху вниз; в данном случае получается число 11001000000. * На рис. 2.3 также дан пример преобразования числа 1331, ко- | торое в двоичной форме выглядит как 10100110011. | Нахождение десятичного числа по его двоичному коду на- зывается преобразованием из двоичной формы в десятичную. i Такое преобразование выполняется довольно просто. Выпишем двоичные цифры преобразуемого числа, как это показано на рис. 2.4, и над каждой цифрой, начиная с самой первой, напи- 2—589 им ГОРЬКОГО БИБЛИОТЕКА . ч М Г ' /
18 Статья 2 шем последовательные степени 2, взяв их из рис. 2.2. Теперь сложим те степени 2, которые написаны над 1 в двоичном числе. Из рис. 2.4 видно, что числа, которые мы ранее преобразовыва- ли в двоичную форму, теперь преобразованы назад в деся- тичную. 1024 512 256 128 64 32 16 8 4 2 1 1 1 0 0 1 0 0 0 0 6 0 1024 4- 512 4- 64 = 1600 1024 512 256 128 64 32 16 8 4 2 1 1 0 1 0 0 1 1 0 0 1 1 1024 4- 256 + 32 4- 16 4- 2 + 1 = 1331 Рис. 2.4. Преобразование чисел из двоичной формы в десятичную. Двоичный разряд носит название бит. Так, двоичное число 11001000000 занимает 11 бит. Упражнения 1. Преобразуйте следующие десятичные числа в двоичные: * а) 100; б) 39; 7^- * в) 128. 2. Преобразуйте следующие двоичные числа в десятичные: а) 1000110; * б) 10001100; в) 111111. 3. Приведенное ниже сообщение было закодировано соглас- но второму способу из статьи 1 с последующим преобразовани- ем каждого десятичного числа в двоичное. Расшифруйте это со- общение. 1001 1 1101 1100 101 1 10010 1110 1001 1110 111 1 10 1111 10101 10100 И 1111 1101 10000 10101 10100 101 10010 10011.
Двоичные сложение и вычитание 19 Статья 3 Двоичные сложение и вычитание Числа в ЭВМ представляются в двоичной форме, в этой же форме выполняется их сложение и вычитание. Рассмотрим, как это делается. Правила сложения и вычитания многоразрядных чисел в двоичной системе точно такие же, как и в десятичной» только 10 заменяется на 2. Рассмотрим процесс сложения двух чисел: 6543 + 2481 9024 Выполняем действия справа налево в следующем порядке: 1) 3 плюс 1 равно 4; 2) 4 плюс 8 равно 12. Записываем 2 и переносим 1; 3) 1 плюс 5 плюс 4 равно 10. Записываем 0 и переносим 1; 4) 1 плюс 6 плюс 2 равно 9. Сложение в двоичной форме выполняется точно так же: 1010 + ПО 10000 Снова выполняем действия справа налево: 1) 0 плюс 0 равно 0; 2) 1 плюс 1 равно 10 (вспомним, что 2 в двоичной системе представляется как 10). Записываем 0 и переносим 1; 3) 1 плюс 0 плюс 1 равно 10. Записываем 0 и переносим 1; 4) 1 плюс 1 равно 10. Поскольку это последнее действие, записываем 10 точно так же, как сделали бы это в десятичной системе. Воспользовавшись рис. 2.1, убеждаемся в правильности ре- зультата. Действительно, мы выполняли сложение 10+6 = 16. Преобразовав все три числа в двоичную форму, вместо 10 по- лучим 1010, вместо 6— ПО и вместо 16— 10000. Следует заметить, что десятичное число можно иногда спу- тать с двоичным. Выполняя сложение 10+6=16, мы предпола- гаем, что число 10 десятичное, а не двоичное. Чтобы не было путаницы, можно число заключить в скобки и приписать ему в виде индекса основание системы счисления—10 или 2. Тогда 2*
20 Статья 3 (10) 2 обозначает двоичное число 10, а (10)ю — десятичное. Правила вычитания также одинаковы в двоичной и десятич- ной системах. Рассмотрим пример 3528 - 1574 1954 Здесь, выполняя действия справа налево, получим 1) 8 минус 4 равно 4; 2) 12 минус 7 равно 5. Занимаем Г, 3) 14 минус 5 (не 15 — 5, поскольку 1 была занята) равно 9. Занимаем еще 1; 4) 2 минус 1 (не 3 минус 1) равно 1. Точно так же производится вычитание двоичных чисел: 1100 - НО но Выполняем справа налево: 1) 0 минус 0 равно 0; 2) 10 минус 1 равно 1 (то есть 2 минус 1 равно 1). Зани- маем 1; 3) 10 (не 11, поскольку мы заняли 1) минус 1 равно 1. Воспользовавшись снова рис. 2.1, проверяем результат: 12 — 6=6. Рассмотренные действия показаны на рис. 3.1. Заме- тим, что число 6 можно было представить в виде ОНО (вместо ПО); тогда пример выглядел бы следующим образом: 1100 — ОНО оно Дополнительный нуль в начале числа ОНО называется ли- дирующим нулем. Лидирующие нули не влияют на значение двоичного числа, но часто используются, особенно если мы хо- тим, чтобы все наши двоичные числа состояли из одного и того же числа битов. Рассматривая рис. 2.1, можно заметить, что добавление нуля в конце двоичного числа эквивалентно умножению этого числа на 2. Так, например, двоичное число НО представляет 6, а дво- ичное число 1100—12, т. е. 6>{«2. Точно также выполняется ум- ножение десятичных чисел на 10: добавление нуля к 6 дает 60, т. е. 6>|с10. Отсюда, между прочим, следует, что все четные дво- ичные числа оканчиваются нулем, а нечетные—1.
Двоичные сложение и вычитание 21 ‘4 2 i О Рис. 3.1. Сложение и вычитание чисел в десятичной и двоичной формах.
22 Статья 4 Упражнения 1. Выполните сложение следующих двоичных чисел: а) 100010 * б) 1101 в) 110110 4- 10101 + 1011 4-1010 2. Выполните вычитание в двоичной форме: * а) 110111 б) 100110 4с в) 100101 - 100100 - 1011 — 111 3. Сложите указанные двоичные числа; затем преобразуйте каждое число в десятичную форму и убедитесь, что сумма де- сятичных чисел, преобразованная в двоичную форму, совпадает с полученной ранее двоичной суммой: 11101 1011 ПО 101 1000 11 11001 (Следующая процедура позволит вам упростить вычисления. Предположим, что сумма битов в каком-то столбце составляет 6. В двоичной форме это ПО, значит надо записать 0 и перенести 11. Но проще перенести 3 (половину от 6) как десятичное число и прибавить к нему биты следующего столбца. Записывается в данном случае 0, так как 6 — четное число, но в случае нечет- ной суммы записывается 1. Только для самого левого столбца сумму следует записать в двоичной форме.) Статья 4 Шестнадцатеричная система Выше упоминалось, что 2 — это основание двоичной системы. Это означает, что при записи любого числа используются всего две цифры — 0 и 1 и степени 2. Аналогично 10 — это основание десятичной системы. Имеются и другие системы с основаниями.
Шестнадцатеричная система отличными от 2 и 10. Чаще других используется шестнадцате- ричная система счисления с основанием 16. В шестнадцатеричной системе используются 16 цифр — 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, А, В, С, D, Е и F. Числа в шестнадцате- ричной системе образуются так же, как и в десятичной, но вме- Двоичное 1000101011010010 1000 1010 1101 0010 Двоичные 0000 Шестнад- цатеричные 0 8 A D 2 0001 1 0010 2 Шестнадцатеричное 8AD2 ООП 3 0100 4 0101 5 Шестнадцатеричное 8AD2 ОНО 6 0111 7 8 A D 2 1000 8 1000 1010 1101 0010 1001 9 1010 А Двоичное 1000101011010010 1011 В 1100 С 1101 D 1110 Е 1111 F Рис. 4.1. Преобразование двоичных чисел в шестнадцатеричные и обратно. сто 10 используется 16. Так, если в десятичной системе мы имеем 3*103 4-4* 102 3456= +5^Ю1 4-6*10° (см. статью 2), то в шестнадцатеричной системе: 3*163 3*4096 12288 3456= 4-4* 162 = 4-4* 256 = 4- 1024 + 5*16' +5* 16 4- 80 4-6*16° 4-6- 4- 6 13398
24 Статья 4 В дальнейшем нам понадобятся только четыре первые сте- пени: 16: 161 = 16, 162 = 256, 163 = 4096 и 164=65536. Так шестнадцатеричное число преобразуется в десятичное. Для того чтобы преобразовать десятичное число в шестнадцате- ричное, надо сначала преобразовать его в двоичное (см. рис. 2.3), а затем полученное двоичное число преобразовать в шестнадцатеричное по следующим правилам: 1) делим двоичное число с правой стороны на группы по четыре бита; 2) преобразуем каждую группу в шестнадцатеричную цифру (рис. 4.1). Для преобразования шестнадцатеричного числа в двоичное те же действия выполняются в обратном порядке (рис. 4.1): 1) выражаем каждую шестнадцатеричную цифру в виде четверки битов; 2) объединяем полученные группы битов в двоичное число. Как видно из рис. 4.1, преобразовать двоичное число в шест- надцатеричное, или наоборот, весьма просто. Посмотрим, по- чему это так. Возьмем двоичное число 1000101011010010; для него можно написать (1000101011010010)2= 1*215+0*214 + 0*213+0*212 + 1*211+0*21°+1*29+0*28 + 1*27+1*26 + 0*25+1*24 + 0* 23 + 0*22+ 1*2'+0* 2° = 212* (1*23 + 0 * 22 + 0* 21+0*2°) + 28*(1*23 + 0* 22+1*21+0* 2°) + 24*(1*23,+1*22 + 0* 21 + 1*2°) +2°* (0*23*0* 22+1*2* + 0 * 2°) = 212* 8 + 28*10 + 24*13+2°*2 = (24)3*8+ (24)2*10+ (24)1*13+(24)°*2 = 163 * 8+162*10+16>*13+16°*2 = (8AD2)ie, т. е. шестнадцатеричное число 8AD2. Таким образом, при преобразовании используется то обстоя- тельство, что 16 является целой степенью 2. Шестнадцатеричная система очень широко используется при программировании на машинном языке. Объясняется это тем, что работать с большими двоичными числами очень неудобно. Записывая, например, двоичное число 1011011101001000, легко ошибиться из-за большого количества единиц и нулей. Поэтому, учитывая простоту преобразования, лучше все двоичные числа записывать в шестнадцатеричной форме, если только в силу
Шестнадцатеричные сложение и вычитание 25 каких-то причин не требуется анализ отдельных битов. На- пример, изменение значения самого левого бита в шестнадца- теричном числе С8 сделает из него 48, поскольку С8 — это дво- ичное 11001000, и изменение значения левого бита дает 01001000, что в шестнадцатеричной форме и составляет 48. Упражнения 1. Преобразуйте следующие десятичные числа в шестнадца- теричные: * а) 100; б) 781 * в) 4096. 2. Преобразуйте следующие шестнадцатеричные числа в де- сятичные: а) 9А; * б) 123; в) АСЕ. >|< 3. Преобразуйте следующую строку двоичных цифр в строку шестнадцатеричных: 10101111101010111100101010110100110110101101 Статья 5 Шестнадцатеричные сложение и вычитание Не только двоичная и десятичная, но и все подобные1) си- стемы счисления имеют схожие правила сложения и вычитания. Шестнадцатеричная система отличается от десятичной только тем, что 10 надо заменить на 16; в остальном правила те же. Пусть надо сложить шестнадцатеричные числа: В785 + 2С84 Ё409 1> Позиционные. — Прим, перев.
26 Статья 5 Сложение выполняется справа налево следующим образом: 1) 5 плюс 4 равно 9 (как и в десятичной системе); 2) 8 плюс 8 равно 10 (чему в десятичной системе соответст- вует 8+8 = 16). Записываем 0 и переносим 1; 3) 7 плюс С равно 13 (в десятичной системе этому соответ- ствует 7+12=19). 13+1 (от переноса) равно 14, записываем 4 и переносим 1; 4) В плюс 2 равно D, плюс 1 от переноса — Е (в десятичной системе 11+2+1 = 14). Рассмотрим теперь пример с вычитанием: 834Е - 2Е5А 54F4 На этот раз процедура такова: 1) Е минус А равно 4 (в десятичной форме 14—10=4); 2) 4 минус 5 требует занять 1, после чего 14 минус 5 рав- но F (не 9 — будьте внимательны!) (в десятичной форме 14—5 превращается в 20 — 5, т. е. 15, что представляет собой шестнад- цатеричное F); 3) 3 минус Е требует занять 1, получается 13 минус Е, од- нако 1 из этого разряда была занята, следовательно, остается 12 минус Е, что равно 4- (,в десятичной форме 18—14=4); 4) 8 минус 2, а с учетом занятой 1, 7 минус 2, равно 5, как и в десятичной форме. Выполняя сложение шестнадцатеричных чисел, не забывай- те, что перенос возникает, только если сумма превышает F. Так, в десятичной системе 25+25=50, но в шестнадцатеричной 25+25=4А, а не 5А (5+5=А без всякого переноса). Преобразование десятичных чисел в шестнадцатеричные и обратно можно осуществить и с помощью таблиц. Так, таблица П2 (см. приложение) позволяет преобразовывать четырехраз- рядные шестнадцатеричные числа в пятиразрядные десятичные и наоборот. Некоторые шестнадцатеричные числа, например В5 или DEF7, напоминают по виду имена переменных, и следует поза- ботиться о том, чтобы не возникало путаницы. Например, си- стема LISA требует записи шестнадцатеричного числа В5 в виде $ В5, чтобы отличить его от переменной с именем В5. Дру- гие системы, например программа-монитор Эпл, предполагают, что все величины даются в шестнадцатеричной форме, и знак $ оказывается ненужным (и даже незаконным). Большинство си- стем программирования на Бейсике не используют шестнадца- теричных чисел, и В5 в Бейсике всегда обозначает имя перемен- ной, а не шестнадцатеричную константу.
Регистры, ячейки и байты 27 Двоичные и шестнадцатеричные числа можно умножать и делить; этот вопрос будет рассмотрен в статье 38. Дроби, на- пример 1/2, также можно выразить в двоичной или шестнадца- теричной форме; этот вопрос рассматривается в статье 97. Упражнения 1. Выполните сложение следующих шестнадцатеричных чисел: а) 137 * б) В159 в) FADE + 652 + 2551 + BEAD 2. Выполните вычитание в шестнадцатеричной форме: * а) 519 б) С650 * в) FEED - 304 -1881 - FACE 3. Сложите приведенные ниже шестнадцатеричные числа; преобразуйте каждое из них в десятичное, сложите полученные десятичные числа и удостоверьтесь, что обе суммы совпадают: 36В С2 4F 980 D Е7 15А Статья 6 Регистры, ячейки и байты Представим себе, что вычислительная система 6502 (или любая другая ЭВМ) содержит большое количество коробочек для хранения чисел. В каждую коробочку помещается 8 бит, поэтому она может содержать любое число от 00000000 до
28 Статья 6 11111111 (при этом только одно число в любой момент вре- мени) . Имеются два вида коробочек. Коробочки одного вида назы- ваются регистрами. В процессоре 6502 много регистров, но мы пока ограничимся рассмотрением трех основных регистров, име- ющих обозначения А, X и Y. Коробочки другого вида носят 0 0 0 0 0 7 О О О\О О О / О О / Рис. 6.1. названия ячеек, и все ячейки, вместе взятые, образуют основ- ную, или оперативную память. Максимальное число ячеек (без привлечения специальных технических средств) составляет 65536, или 216, хотя имеются системы 6502 с меньшим числом ячеек. / О О 1 0 0 О 0 0 0 Регистр Л Рис. 6.2. 0 0 0 0 0 1 Регистр А Каждый регистр или ячейка может содержать любое двух- разрядное шестнадцатеричное число от 00 до FF. (FF)i6 = = (255) ю, поэтому каждый регистр или ячейка может содер- жать любое десятичное число, не превышающее 255. Заметьте, что 255=256—1 =28— 1. Число 28 представляется в двоичной системе как 100000000, т. е. единица, за которой следует восемь нулей. Если из этого числа .вычесть 1, получится 11111111. Это иллюстрирует общий принцип двоичной системы: если имеются п бит (у нас п=8), то с их помощью можно записать любое число от 0 до 2"—1 (у нас до 28—1). Этот принцип бу- дет часто использоваться в дальнейшем изложении. Ячейки оперативной (основной) памяти имеют номера, на- зываемые адресами, отсчет которых начинается с нуля. Так, имеется ячейка с номером 0, ячейка с номером 1 и т. д.; к этим ячейкам можно обратиться по адресам 0, 1 и т. д. V Максималь- но возможный адрес равен 65535, или 216—1, поскольку макси- мальное число ячеек, включая ячейку с адресом 0, составляет 65 536. Согласно высказанному выше принципу, любой адрес в пределах от 0 до 216—1 можно записать с помощью 16 бит Ячейка с адресом 59 является пятьдесят девятой ячейкой ЭВМ (не счи- тая ячейки 0), так же, например, как дом № 59 по Элм-стрит является (тео- ретически) пятьдесят девятым домом на этой улице.
Регистры, ячейки и байты 291 в двух ячейках или, если нужно, в двух регистрах (например, А и X). Рассмотрим пример такой операции. Как было показано вы- ше (см. рис. 2.3), десятичному числу 1600 соответствует двоич- ное 11001000000. Запишем это число в 16 двоичных разрядах, добавив лидирующие нули (рис. 6.1). Теперь разместим поло- вину этого числа в регистре А, а половину — в регистре X (рис. 6.2). Число, записанное теперь в регистре А, соответствует десятичному 6, а число в регистре X — десятичному 64. Вместе, однако, эти регистры содержат десятичное 1600, занимающее 16 двоичных разрядов. Для получения этого числа надо вычис- лить значение выражения 256*А+Х: 256*6+64=1536+64= 1600. Помимо адресов, или номеров, отдельные ячейки в програм- ме имеют также имена. В сущности это имена переменных, так же как и в языке Бейсик. Если у вас есть ячейка с именем К, вы можете хранить в ней значение К. Это значение может из- меняться; например, в программе на Бейсике FOR К=1 ТО 100 (следующие предложения) NEXT К переменная К последовательно принимает значения от 1 до 100. Все эти значения можно хранить в ячейке с именем К; напри- мер, если К равно 55, то в этой ячейке содержится число 55. В других ЭВМ регистры могут содержать не 8 двоичных раз- рядов, а больше, например 24 разряда, 36 разрядов и т. д. Одна- ко 8-разрядные регистры встречаются очень часто, и для после- довательности из 8 двоичных разрядов (8 бит) придумано спе- циальное название байт, введенное, видимо, фирмой IBM. Понятия «один байт» и «8 бит» равнозначны. Для хранения адреса, таким образом, требуется два байта. Если ячейки ЭВМ содержат по 8 двоичных разрядов, как в системе 6502, то слова «ячейка» и «байт» можно считать синонимами; например, мы говорим 65536 байт памяти, или, короче, 64К байт. Здесь К обозначает «умноженное на 1024», и 1 килобайт (или Кбайт) — это то же, что 1024 байт или 210 байт1). Кроме оперативной памяти, ЭВМ может быть оснащена вспомогательной (внешней) памятью. Магнитная дискета, на- пример, содержит свыше 105 байт памяти. Такую память, одна- *> Используется также величина мегабайт, или Мбайт, т. е. 220=1048596> байт. Однако оперативная память систем 6502 не достигает такого объема. Если в ЭВМ ячейка оперативной памяти содержит больше 8 разрядов, она часто называется словом. Бывают 16-разрядные слова, 32-разрядные слов» и т. д.
30 Статья 7 ко, нельзя использовать непосредственно; предварительно ее содержимое надо переписать в оперативную память. Этот про- цесс считывания с дискеты будет обсуждаться позже, в статьях 91 и 92. Не путайте адрес ячейки с содержащимся в ней числом. На- пример, ячейка с адресом 6 может содержать любое число от 0 до 255, и совсем не обязательно число 6. (Адрес 6 у ячейки оз- начает лишь, что это шестая ячейка ЭВМ, и ничего более). Упражнения 1. Какие из приведенных чисел могут содержаться в реги- стре А? а) 200; * б) 300; в) 400. 2. Каждое из приведенных чисел должно храниться в двух регистрах — А и X. Какая величина будет содержаться в реги- стре А, и какая — в регистре X (числа десятичные)? Яс а) 256; б) 1000; * в) 10000. 3. Одна из старых ЭВМ под названием PDP-8 имела 12-раз- рядные регистры. Какое максимальное десятичное число можно записать в такой регистр? Статья 7 Многобайтовые величины и дополнение до двух Может показаться, что при использовании 8-разрядных яче- ек мы сталкиваемся с трудностью, поскольку в них можно хра- нить лишь числа от 0 до 255. Мы не можем записать в такую ячейку большое число (больше 255) или отрицательное число (меньше 0). Для хранения в ЭВМ больших и отрицательных чисел используются специальные приемы, к изучению которых мы сейчас приступим. Для хранения больших чисел можно использовать несколько ячеек для каждого числа. Если взять две ячейки, как это приш-
Многобайтовые величины и дополнение до двух 3> лось нам сделать для записи адреса, то диапазон записываемых чисел увеличится до 216—1, или 65535. Пусть одна ячейка со- держит число р, а другая — число q\ тогда обе ячейки вместе содержат число 256р+</ — это совпадает с выражением 256А+Х, приведенным в предыдущей статье. .Если же исполь- зовать более двух ячеек, например п ячеек, то появится воз- можность хранить любые числа от 0 до 28п—1. Если, например, и=4, то 28п—1=232—1, что превышает 4 000000 000. Десятичные Двоичные Шестнадца- теричные -5 11111011 FB -4 11111100 FC -3 11111101 FD -2 11111110 FE -1 11111111 FF 0 00000000 00 1 00000001 01 2 00000010 02 3 00000011 03 4 00000100 04 5 00000101 05 Рис. 7.1. Дополнение до двух целых чисел. Отрицательные числа можно записывать двумя способами. Первый заключается в том, что самый левый разряд отводится под код знака: 0 означает положительное число, а 1—отрица- тельное. В 8-разрядной ячейке 10000011 будет представлять — 3: первая 1 означает, что число отрицательно, а остальная часть содержимого ячейки, 0000011, представляет собой 3 (с 5 лиди- рующими нулями). Такая Запись называется знаковым пред- ставлением в прямом коде. Этот способ обычно не используется в системах 6502; он применялся главным образом в ЭВМ нача- ла 60-х годов. Другой способ представления отрицательных чисел напоми- нает работу 3-разрядного счетчика, используемого во многих бытовых магнитофонах. При обратной перемотке ленты счетчик считает назад; после 000 счетчик показывает 999, затем 998 и т. д. Точно так же, если мы будем вычитать по 1 из 8-разряд- ного двоичного числа, то после 00000000 будет 11111111, затем 11111110 и т. д. В результате 11111111 можно считать представ- лением — 1, 11111110 — представлением —2 и т. д. Полученные
32 Статья 7 таким образом представления чисел от —5 до +5 в десятичной, двоичной и шестнадцатеричной формах приведены на рис. 7.1. Представление — х можно найти, вычтя х из двоичного 100000000. Так, например, десятичному 45 соответствует двоич- ное 101101. Вычтя это число из 100000000, получим 11010011. Легче, однако, вычесть х из двоичного 11111111, а затем доба- вить 1 к результату. (Это эквивалентно, поскольку 111111114* 4-1 = 100000000.) Таким образом, возможны два пути: 1) 100000000 либо 2) 11111111 - 101101 — 00101101 11010011 11010010 +_________L 11010011 Второй способ проще, так как избавляет от необходимости занимать 1 в старшем разряде. Каждая операция вычитания представляет собой либо 1—0 = 1, либо 1 —1=0. Фактически вам даже не нужно вычитать: просто вместо каждого 0 запи- шите 1, а вместо каждой 1 — 0. Такая операция называется на хождением обратного кода числа, или дополнения до единицы (обратный код 0 есть 1; обратный код 1 есть 0). Прибавление же к обратному коду 1 дает нам дополнение до двух1). Таким образом, использование системы представления до- полнением до двух дает возможность записывать в d-разрядном регистре отрицательные числа — х, как дополнение х до двух, или 2d—х. Как и в случае знакового представления в прямом коде, положительные числа начинаются с 0, отрицательные — с 1. Если d=8, то самое большое число в рассматриваемом представлении равно 01111111 (десятичное 127), а самое ма- ленькое — 10000000 (десятичное —128). В общем случае диапа- зон представленных чисел заключен от —2d-1 до 2d-1—1; числа в этом диапазоне называются числами, со знаком. Число 45 в системе представления дополнением до двух есть 00101101; чис- ло —45 в системе представления дополнением до двух есть 11010011 (другими словами, это есть дополнение до двух числа 45). Дополнение до двух отрицательного числа находится точно так же: к обратному коду числа прибавляется (не вычитает- ся!) 1; так, обратный код числа 11010011 есть 00101100, и послеi прибавления 1 получаем 00101101. 1 Система представления чисел дополнением до двух исполь- зуется практически во всех современных ЭВМ. Она удобна тем, что операции сложения чисел со знаком и без знака, как это) > Этот термин, строго говоря, неправилен и сохраняется в силу исто- рических причин. Дополнение до двух совсем не означает вычитание х из 22222222; вы просто прибавляете 1 к дополнению х до единицы.
Многобайтовые величины и дополнение до двух 33 будет показано в статье 88, выполняются одинаково (рис. 7.2). Заметьте, что если код 11111011 выражает число без знака, то он не может обозначать —5 и должен, следовательно, рассмат- риваться как 251; если код выражает число со знаком, он не может обозначать 251 (вспомним, что числа со знаком лежат Беззнаковое представление 3 + 2 5 1 Двоичное представление Знаковое представление 3 + (-3) -г 2 5 Ч в диапазоне от —128 до 127) и должен, следовательно, рассмат- риваться как —5. Мы часто используем двоичное число для обозначения двух различных чисел (в приведенном примере 11111011 обозначает и 251, и —5), и, как видно из примера, никакой путаницы не возникает. Однако мы должны помнить, какого рода данные представляют наши двоичные числа, пото- му что нет никакого способа определить, что хранится в реги- стре или ячейке: число со знаком, число без знака или еще какое-то данное1). Процессор 6502 может также выполнять вычисления с дро- бями и другими действительными числами. Этот вопрос будет рассмотрен в статье 98. Упражнения 1. Выразите следующие десятичные числа в двоичной форме так, как они хранились бы в регистре А, используя систему представления дополнением до двух: а) -23; * б) 54; в) —1. 2. Выразите следующие двоичные числа, считая, что они записаны в системе представления дополнением до двух, в фор- ме десятичных чисел со знаком (не забудьте, что число со зна- ком не обязательно отрицательно): * а) 11101000; б) 01110111; * в) 10000011. *> Правда, в некоторых языках очень высокого уровня, например в Лисп и Снобол, такая проверка возможна. В этих языках используется специаль- ный прием, который мы обсудим в статье 100. 3—589
34 Статья 8 3. Предположим, что двоичные числа предыдущего упраж- нения представляют собой числа без знака. Выразите их в де- сятичной форме. Статья 8 Загрузка регистров и запись в память Рассмотрим предложение Бейсика L = K. Это предложение устанавливает переменной L новое значение, равное К. Если К=55, то и новое значение L будет 55. В ЭВМ системы 6502 мы должны для этого переслать число из ячейки с именем К в ячейку с именем L. Предположим на время, что если К и L — величины без знака, то 0^К^255 и 0^L^255; если же К и L — величины со знаком, то — 128=^К=С 127 и —128^1^127. В статье 12 это ограничение будет снято. Пересылка числа из К в L выполняется в два этапа: 1. Пересылка числа К в регистр. Эта операция называется загрузкой регистра. (Вы загружаете в регистр число так же, как могли бы загрузить покупки в свою машину.) 2. Пересылка числа из регистра в ячейку L. Эта операция называется записью в память*). (Вы сохраняете число, находив- шееся в регистре, так же, как могли бы сохранить покупки, вы- нутые из машины.) В процессоре 6502 предусмотрены шесть команд для за- грузки регистров и записи в память (команда процессора 6502, иногда называемая предложением, напоминает предложение Бейсика). Эти команды имеют следующий вид: LDA v Загрузка v в регистр А LDX v Загрузка v в регистр X LDY v Загрузка v в регистр Y STA v Запись содержимого регистра А в ячейку v STX v Запись содержимого регистра X в ячейку v STY v Запись содержимого регистра Y в ячейку v После выполнения операции записи число будет храниться в памяти. — Прим, перев.
Загрузка регистров и записи в память 35 Здесь LD обозначает «load» (загрузить); ST обозначает «store» (записать); v может обозначать любую ячейку памяти. Таким образом, предложение Бейсика L=K можно выпол- нить тремя способами. Если мы хотим использовать регистр А, мы можем написать следующие строки программы на языке ассемблера: LDA К STA L Если же мы хотим использовать регистр X или регистр Y, то надо написать: LDX К или LDY К STX L STY L В регистр можно загрузить константу Предположим, что вместо предложения L=K мы хотим выполнить предложение Бейсика L=100. Для этого мы должны написать (используя регистр А): LDA #1100 STA L Обратите внимание на специальный знак #, который надо обя- зательно использовать перед константой. Специальный знак ! указывает на то, что 100 — десятичное число. Для обозначения двоичных чисел используется знак %, а для обозначения шест- надцатеричных—'Знак $: LDA # % 01100100 или LDA #$64 STA L STA L И в том и в другом примере в ячейку L записывается двоичный эквивалент десятичного числа 100. При желании можно исполь- зовать регистр X или регистр Y: LDX #1100 или LDY #1100 STX L STY L В Бейсике, как известно, выполнение операции L=K не из- меняет К, а только присваивает значение К переменной L. Для процессора 6502 справедливы те же правила. Если мы загру- жаем К в регистр, К не изменяется, и, если мы записываем со- ’> Константа должна быть такой, чтобы число разрядов в ее двоичной форме не превышало длины регистра. Поэтому для константы k без знака должно выполняться условие 0^6^255; для константы k со знаком должно выполняться условие—128^6^127. 3*
36 Статья 8 держимое регистра в L, содержимое регистра остается тем же. Рассмотрим, например, команды LDA Р STA Q STA R Мы загружаем Р в регистр А, затем записываем Р в Q и еще раз записываем Р в R. Поскольку после записи в R в регистре А остается Р, это эквивалентно следующей записи на языке Бейсик: Q = Р R = Р Предположим теперь, что мы хотим число из К переслать в L, а число из L переслать в К (если, например, К=3 и L=8, то после выполнения указанных операций получим К=8 и L=3). Эту операцию (обмен) можно выполнить следующим об- разом: LDA К LDX L STA L STX К В этом примере 4 команды. Сначала мы загружаем К в ре- гистр A, a L — в регистр X. Затем содержимое регистра А (т. е. значение К) записываем в L и, наконец, содержимое регистра X (т. е. значение L) записываем в К. Не забудьте, что регистр в каждый момент времени может хранить одно и только одно число. Если, например, вслед за командой LDA К вы сразу же написали LDA L, то значение К в регистре А будет потеряно. Упражнения 1. Напишите последовательности команд процессора 6502, соответствующие приведенным ниже предложениям Бейсика (учтите, что, как и в любом другом языке программирования, все имена переменных пишутся на одной строке, т. е., например, В5 или ВЗ, а не В5 и В3): * а) В5=ВЗ; б) D9=57; * в) Z=0. 2. Напишите предложения Бейсика, соответствующие приве- денным ниже последовательностям команд процессора 6502 (учтите, что в Бейсике не используются шестнадцатеричные кон- станты) :
Инкремент и декремент 37 а) LDA С1 STA С2 * б) LDX #!50 STX W STX W2 в) LDY #S5D STY К >|< 3. Напишите последовательность команд процессора 6502, реализующую «тройной обмен» между Р, Q и R: Q присваива- ется старое значение Р, R— старое значение Q, а Р — старое значение R (заметьте, что тройной обмен не эквивалентен трем простым обменам). Вы можете использовать регистры А и Y, но не регистр X (другими словами, не используйте команды LDXhSTX). Статья 9 Инкремент и декремент Любая ЭВМ предоставляет возможность сложения и вычи- тания. В программах для процессора 6502 при сложении и вы- читании используются специальные приемы, которые будут рас- смотрены в статьях 15 и 17. Здесь мы познакомимся с двумя простыми операциями: прибавление 1 (эта операция известна под названием инкремент) и вычитание 1 (так называемый декремент). В процессоре 6502 предусмотрены шесть команд инкремента и декремента: INC v Инкремент v DEC v Декремент v INX Инкремент регистра X DEX Декремент регистра X INY Инкремент регистра Y DEY Декремент регистра Y
38 Статья 9 Здесь, как и раньше, v обозначает любую ячейку оператив- ной памяти. Заметьте, что мы не можем выполнить инкремент или декремент регистра А. Выполним с помощью указанных команд предложение Бей- сика K=L+1. Для этого понадобятся три шага: 1. Загрузка L в регистр. 2. Инкремент регистра (прибавление 1 к его содержимому). 3. Запись содержимого регистра в К. При использовании регистра X потребуется такая последова- тельность команд: LDX L INX STX К Точно так же можно выполнить операцию U=V— 1, используя, например, регистр Y: LDY V DEY STY U Если же нам надо выполнить операцию К=К+1 (т. е. приба- вить 1 к К), мы можем ограничиться только одной командой: INC К Заметьте, что последовательность команд INC L LDA L STA К также выполняет операцию K=L+1. Однако такое решение не является наилучшим, так как одновременно с К изменяется L, хотя, записывая K=L+1> мы предполагаем, что изменяется только К. Изменение в этом случае L можно назвать побочным эффектом (если, например, вы принимаете аспирин от головной боли, и головная боль проходит, но расстраивается желудок, то это тоже является побочным эффектом). Команды инкремента и декремента часто используются для прибавления или вычитания 2. Для того чтобы выполнить опе- рацию K=L-|-2, надо написать LDX L INX INX STX к
Инкремент и декремент 39 Выполнить К=К—2 можно проще: DEC К DEC К Таким же образом можно прибавлять или вычитать 3, 4 и т. д., но обычно для этого используются команды сложения и вычи- тания (см. статьи 15 и 17). Важно помнить, что команды INC К и DEC К не изменяют содержимого регистра А. Так, последовательность команд INC К и STA L не сделают L равным K-j-1. Инкремент 255 дает 0, а декремент 0 дает 255. Действитель- но, если регистр X содержит 0 (двоичное число 00000000), то в результате выполнения команды DEX в регистре X теперь бу- дет 11111111 (что представляет собой число без знака 255 или число со знаком —1, см. статью 7). Точно так же, если регистр Y содержит двоичное число 11111111, то в результате выполне- ния команды INY в регистре будет 0. Упражнения 1. Напишите последовательности команд процессора 6502, соответствующие следующим предложениям Бейсик: a) F6=F44-1 * б) F4=F4-+-1 в) W=W-1 2. Напишите предложения Бейсика, соответствующие при- веденным ниже последовательностям команд процессора 6502: * а) LDX K4 DEX STX КЗ б) DEC C DEC C * в) LDY S STY J INY STY C 3. Найдите две команды процессора 6502, эквивалентные следующим трем командам: LDX' #$30 DEX STX В
1 40 Статья 10 Статья 10 Машинный язык и язык ассемблера В оперативной памяти ЭВМ системы 6502 могут храниться, кроме кодов чисел, еще и коды команд. Каждая команда про- цессора имеет свой код, который мы всегда будем выражать в шестнадцатеричной форме и который может занимать 1, 2 или 3 ячейки оперативной памяти. Например, команде LDA # $64 соответствует код А9 64 при этом А9 (двоичное 10101001) размещается в первой ячейке, а 64, или двоичное 01100100,— во второй. Код команды STA L зависит от адреса L (т. е. от адреса ячейки, содержащей L). Вспомним, что каждая ячейка имеет номер, или адрес, занимающий 16 разрядов (два байта). Поэто- му он выражается четырьмя шестнадцатеричными цифрами. Предположим, что ячейка, содержащая L, имеет адрес 08С4. Тогда код команды STA L будет таким: 8D С4 08 В первом байте кода команды (2 первые шестнадцатеричные цифры) содержится так называемый код операции. Это код конкретной машинной операции; так, А9 — это код операции «загрузка регистра А константой», a 8D — «запись содержимого регистра А в ячейку памяти с 16-разрядным адресом». Если в команде используется константа (например, LDA Ф$64), код команды размещается в двух байтах, при этом второй байт содержит константу (шестнадцатеричное 64 в при- веденном примере). Такие команды иногда называют команда- ми с непосредственной адресацией. Если в команде используется переменная с 16-разрядным адресом, код команды размещается в трех байтах, причем во втором и третьем байтах содержится адрес переменной. Заметь- те, что эти байты всегда записываются в память в обратном по- рядке. Так, адрес 08С4 записывается в коде команды STA L как С4 08. Это сделано для упрощения электронной схемы про- цессора 6502. Коды команд хранятся в ячейках оперативной памяти, и эти ячейки, естественно, имеют адреса. Предположим, что первый байт А9 команды А9 64 содержится в ячейке с адресом 0807.
Машинный язык и язык, ассемблера 41 Тогда следующий байт 64 содержится в следующей ячейке — в данном случае в ячейке с адресом 0808. Если за командой LDA # $64 сразу следует команда STA L (при выполнении операции L=100), то три байта коман- ды STA L занимают три следующие ячейки с адресами 0809, 080А (вспомните, что адреса представляются в форме шестнад- цатеричных чисел) и 080В. Сказанное можно изобразить в ви- де таблицы: Адрес Содержимое Команда 0807 А9 LDA #$64 0808 64 0809 8D STA L 080А С4 080В 08 Первые два столбца представляют фрагмент программы на машинном языке. Третий столбец — это тот же фрагмент, запи- санный на языке ассемблера. Строки программы на машинном языке часто записывают в уплотненном виде: Машинный язык Язык ассемблера 0807 А9 64 LDA #$64 0809 8D С4 08 STA L Трехбуквенное условное обозначение команды, например LDA, носит название мнемонического или символического обо- значения, или просто мнемоники *>. Так, операция «загрузка ре- гистра А константой» имеет код операции А9 и мнемонику LDA. Мнемонические обозначения облегчают запоминание команд (вами, а не машиной): запомнить LDA (Load А) легче, чем А9. В процессоре 6502 команды с отличающимися кодами опе- рации могут иметь совпадающую мнемонику. Так, операции «за- грузка в регистр А переменной, имеющей 16-разрядный адрес» соответствует код AD, а не А9, но ее мнемоника по-прежнему LDA. Некоторые команды, например INX, содержат только мнемо- нику (не имеют операнда). Машинное представление таких команд включает только код операции (Е8 для INX) и занима- ет соответственно 1 байт. Слово «содержимое» над вторым столбцом приведенной вы- ше таблицы широко используется для обозначения числа, со- » Обратите внимание, что LDA К не является мнемоникой — это исполь- зование мнемоники LDA. Переменйая К носит название операнда команды LDA.
42 Статья 10 держащегося в ячейке С. Это число можно рассматривать как значение переменной С. Мы познакомились с 12 командами процессора 6502: LDA, LDX, LDY, STA, STX, STY, INC, INX, INY, DEC, DEX и DEY. Вообще команд гораздо больше; все они приведены в приложе- нии. В табл. ПЗ поясняется действие каждой команды; в табл. П4 дан алфавитный список команд вместе с их машинны- ми кодами; в табл. П5 команды упорядочены по машинным ко- дам. В последующих статьях будет продолжено изучение команд как по одиночке, так и группами. Концепция хранения команд и данных в памяти ЭВМ в за- кодированной форме является основной концепцией вычисли- тельной техники. Она была предложена приблизительно одно- временно изобретателями ЭВМ — Айкеном, Атанасовым, Эккер- том и Мочли, а также Гольдстайном и фон Нейманом. Эта кон- цепция известна под названием концепции хранимой программы. Упражнения # 1. Предположив, что ячейка, содержащая М, расположе- на по адресу 08DA, а сама программа начинается с адреса 080В, преобразуйте в строки машинного языка следующие команды языка ассемблера: LDA #10 STA М (Никогда не вредно напомнить, насколько важна точность и ак- куратность в мелочах, если дело касается машинного языка или языка ассемблера. Заметьте, что и здесь, и всюду в дальней- шем содержимое любой ячейки на машинном языке всегда должно представляться двумя шестнадцатеричными цифрами). 2. Предположив, что ячейка, содержащая N, имеет адрес 08DB, преобразуйте в строки языка ассемблера следующие ма- шинные команды: 0810 А9 FF 0812 8D DB 08 # 3. Используя предположения обоих предыдущих заданий, напишите предложения языка Бейсик, соответствующие следую- щим машинным командам: 0815 А9 03 0817 8D DA 08 081А 8D DB 08
Ассемблер и псевдокоманды 43 Статья 11 Ассемблер и псевдокоманды Возникает вопрос: как можно определить адрес переменной в конкретной программе и как можно присвоить ему то или иное значение? Эти функции выполняет специальная программа, на- зываемая ассемблером. Ассемблер преобразует (транслирует) язык ассемблера в машинный язык. Вы пишете программу на языкё ассемблера, а ассемблер создает машинную форму этой программы. Только Машинный язык Язык ассемблера ORG $0807 0807 AD С5 08 080А 8D С4 08 LDA L STA К ORG $08С4 08С4 08С5 К DFS !1 L DFS !1 END Рис. 11.1. Машинный язык и язык ассемблера. когда это сделано, и коды команд находятся в памяти, мы мо- жем выполнить программу. В дальнейшем мы будем считать, что у нас имеется какая- либо версия ассемблера LISA. Как и все ассемблеры, эта про- грамма имеет специфические условные обозначения, которые могут не совпадать с обозначениями других ассемблеров. Так, использование специальных символов !, % и $ для обозна- чения десятичных, двоичных и шестнадцатеричных чисел харак- терно именно для ассемблера LISA. Если мы используем ассемблер, мы пишем в своей програм- ме не только команды, или операции, но и так называемые псев- докоманды^. По внешнему виду они напоминают команды, но таковыми не являются; с их помощью ассемблеру передается необходимая информация. Наиболее важными псевдокоманда- 0 Этот оператор часто называют директивой ассемблера, или просто ди- рективой.— Прим, перев.
44 Статья 11 ми ассемблера LISA являются ORG, END и DFS1). Пример их использования приведен на рис. 11.1. Псевдокоманда ORG указывает ассемблеру LISA, с какого адреса начать программу. Если вы пишете ORG п, LISA начи- нает программу с адреса п (который обычно указывается в ви- де шестнадцатеричного числа) и далее использует последова- тельно возрастающие адреса. Так, на рис. 11.1 псевдокоманда ORG $0807 указывает, что ассемблер должен начать програм- му с адреса 0807; следующая команда (LDA L) после трансля- ций в машинный язык (AD С5 08) занимает ячейки 0807 (см. ле- вый столбец рис. 11.1), 0808 и 0809. Следующая ячейка имеет адрес 08 ОА (не 0810 — будьте внимательны!), так что следую- щая команда (STA К) после трансляции в машинный язык (8D С4 08) занимает ячейки 080А (опять см. левый столбец рис. 11.1), 080В и 080С. Как уже говорилось в предыдущей статье, AD представляет собой код операции «загрузки регистра А переменной, имеющей 16-разрядный адрес», и это отличается от кода А9 операции «загрузка регистра А константой». Псевдокоманда END должна быть указана один и только один раз в конце программы (см. рис. 11.1). С помощью псевдокоманды DFS указывают для каждой пе- ременной в программе, сколько места она требует в оператив- ной памяти. Если v — некоторая переменная, то псевдокоманда v DFS п определяет, что v занимает п байтов, или ячеек, в опе- ративной памяти (ассемблер LISA 2.5, получив псевдокоманду v DFS п, 0, запишет во все эти байты начальное значение 0). На рис. 11.1 использованы псевдокоманды ORG $08С4 и К DFS II. Вторая псевдокоманда означает, что К занимает одну ячейку, именно (см. левый столбец) ячейку с адресом 08С4, по- скольку ORG $08С4 требует, чтобы ассемблер продолжил программу с адреса 08С4. Псевдокоманда L DFS II означает, что L занимает одну ячейку — следующую, которая, как это видно из рис. 11.1, имеет адрес 08С5. Приведенная программа состоит из программной секции, со- держащей команды (LDA и STA в примере), и секции данных, содержащей данные (К и L). Каждая секция начинается с пред- ложения ORG. Вплоть до статьи 45, где мы познакомимся с бо- лее общими принципами построения программ, мы будем счи- тать, что каждая программа содержит программную секцию и секцию данных, как в рассмотренном примере. Адреса псевдо- команд ORG должны размещаться между 0800 и 17FF; эту об- ласть LISA отводит под ваши программы. Порядок секций не имеет значения: вначале может располагаться как программная секция, так и секция данных. *> ORG — от Origin, начало; END — от End, конец; DFS — от Define sto- rage, определить память. — Прим, перев.
Ассемблер и псевдокоманды 45 Следите за тем, чтобы программная секция и секция данных не перекрывались. Если программная секция расположена, на- пример, между ячейками 0800 и 0834, секция данных может начаться- с ячейки 0840, но не с ячейки 0820, поскольку любая ячейка может содержать в каждый момент времени только одно число. Обратите внимание на пробелы перед и после ORG, LDA, STA и т. д. В этих местах обязательно должны быть пробелы (один или несколько), однако пробелы нельзя вставлять про- извольно (например, нельзя писать LD А вместо LDA). На языке ассемблера мы всегда можем определить перемен- ную, указав ее адрес. В приведенном примере К расположена по адресу 08С4, поэтому вместо STA К можно было бы напи- сать STA $08С4. В частности, команды LDA 15 или LDA $5 загрузят в регистр А содержимое ячейки с адресом 5. Совершен- но по другому выполняется команда LDA *15 (или LDA *$5), которая загружает в регистр А число 5. Начинающие программисты очень часто забывают поставить знак *. Имейте в виду, что система LISA не отмечает как ошибку пропуск зна- ка *, потому что и без этого знака команда имеет смысл. Полный список псевдокоманд ассемблера LISA с их описа- нием приведен в табл. П6. В следующих статьях псевдокоман- ды будут рассмотрены подробно. В табл. П7 описаны все спе- циальные знаки (* I $ % и др.), используемые LISA. Можно заметить, что знак ! требуется при указании десятичных чисел в LISA 1.5 (и мы будем его использовать повсеместно в даль- нейшем тексте), но в LISA 2.5 знак ! не обязателен и может опускаться!). Упражнения 1. Преобразуйте приведенную семблера в машинные коды: ниже программу на языке ас- ORG $0900 м DFS 11 N DFS И ORG $0910 LDA #по STA М STA END N » Из этого правила имеется исключение: если вы хотите работать с де- сятичными адресами ячеек (например, использовать команду LDA ПО для загрузки регистра А из ячейки с адресом 000А, т. е. десятичное 10), знак ! надо ставить. Впрочем, это не имеет большого значения, поскольку адреса в программе всегда выражаются в шестнадцатеричной форме.
46 Статья 12 >|с 2. Преобразуйте приведенную ниже программу на ма- шинном языке в программу на языке ассемблера, используя псевдокоманды ORG, END и DFS, а также переменную С: 08D0 А9 50 08D2 8D FA 08 3. Преобразуйте приведенную ниже программу на машин- ном языке в программу на языке ассемблера, используя псев- докоманды ORG, END и DFS,-a также переменные Pl, Р2, РЗ: 08DD А9 00 08DF 8D 09 09 08Е2 8D 0А 09 08Е5 8D 0В 09 (Заметьте, что ячейки, занимаемые некоторой переменной, используются только этой и никакой другой переменной.) Статья 12 Двухбайтовые числа и адресные выражения Псевдокоманду DFS можно использовать для определения двухбайтового, или 16-разрядного числа J; для этого надо напи- сать J DFS !2. Старший и младший байты этого числа называ- ются левым полусловом и правым полусловом, или старшим по- лусловом и младшим полусловом. Так, если J равно 08С4, то 08 составляет левое, или старшее, полуслово, а С4 — правое, или младшее, полуслово. Обычное 8-разрядное (положительное) число можно преобра- зовать в 16-разрядное, приписав к нему слева нули. Так, 03 и 0003 представляют собой одно и то же число; 16-разрядное чис- ло 3 содержит 0 в левом полуслове и 3 в правом. Отрицательное число, занимающее 8 разрядов и выражен- ное в форме дополнения до двух, может быть преобразовано в 16-разрядное (также в форме дополнения до двух) приписыва-
Двухбайтовые числа и адресные выражения 47 нием к нему слева 8 двоичных единиц (двоичное 11111111, ше- стнадцатеричное FF или десятичное —1). В статье 7 отмеча- лось, что отрицательное число — х записывается в d-разрядный регистр или ячейку как 2а—х. Если d=16, то это дает 216—х, или в шестнадцатеричной форме 10000—х. Так, например, чис- ло — 8, занимая 8 разрядов, имеет форму F8 (100—8), а в.16 разрядах принимает вид FFF8 (т. е. 10000—8). F8 составляет младшее полуслово, a FF — старшее. Если, например, написать на языке ассемблера ORG $0950 J DFS 12 то два полуслова J будут содержаться в ячейках с шестнадца- теричными адресами 0950 и 0951. Адрес J в этом случае равен 0950. Согласно общему правилу, адрес переменной, которая за- нимает несколько байтов, совпадает с адресом ее первого бай- та .0. Чаще всего двухбайтовые величины, хранящиеся в памяти ЭВМ системы 6502, имеют обратный порядок расположения бай- тов, как это уже описывалось (применительно к адресам) в статье 10. В приведенном выше примере левое полуслово J бу- дет в этом случае в ячейке с адресом 0951; мы можем сказать, что левое полуслово расположено по адресу 0951 (далее все ад- реса будут выражаться в шестнадцатеричной форме). Правое полуслово J расположено по адресу 0950. Предположим теперь, что у нас есть двухбайтовые числа J и N, и мы хотим выполнить операцию N=J. В процессоре 6502 эта операция может быть выполнена только побайтно. Таким образом, мы должны: 1) загрузить в регистр левое полуслово J и записать его в левое полуслово N; 2) загрузить в регистр правое полуслово J и записать его в правое полуслово N (эти операции можно выполнить в любом порядке). С использованием регистра X это потребует следующей про- граммы: LDX J STX N LDX J4-11 STX N4-I1 В ассемблере LISA 1.5 есть ошибка (исправленная в LISA 2.5), в ре- зультате которой это правило иногда нарушается (но только в листинге трансляции, а не в машинных кодах).
48 Статья 12 Выражение J+I1 представляет собой адресное выражение. Если J — это переменная с адресом 0950, то — переменная с адресом 0951. Адресное выражение может содержать константы и перемен- ные. Если в адресном выражении встречается имя переменной, то оно обозначает адрес этой переменной. В приведенном при- мере J обозначает 0950, а прибавление 1 дает адрес 0951. Адрес, вычисленный с помощью адресного выражения, фигу- рирует в программе на машинном языке. Рассмотрим пример двух эквивалентных программ — на языке ассемблера и на ма- шинном языке. Машинный язык Язык ассемблера ORG $0900 0900 АЕ 50 09 LDX J 0903 8Е 52 09 STX N 0906 АЕ 51 09 LDX J + I1 0909 8Е 53 09 STX N+11 ORG $0950 0950 J DFS 12 0952 N DFS 12 END Здесь АЕ представляет код операции «загрузка регистра X переменной, имеющей 16-разрядный адрес». Адрес J равен 0950, и на машинном языке это представляется как 50 09 с перестав- ленными, как и раньше, байтами. Адрес J-f-ll равен 0951, что представляется как 51 09. Вид адресного выражения может ввести вас в заблуждение. Можно подумать, что LDX J-f-ll обозначает загрузку в регистр X значения переменной J, увеличенного на 1. Это, однако, не так; более того, такую операцию вообще нельзя выполнить од- ной командой. Для загрузки регистра X значением J-J-1 надо использовать две команды: LDX J INX Упражнения 1. Напишите программу на машинном языке, соответст- вующую следующей программе на языке ассемблера: ORG $0980 N1 DFS 12 N2 DFS II ORG $09С0
Индексированные переменные и индексные регистры 49 LDA #!0 Г STA N2 STA N1 STA N1+I1 END 2. Преобразуйте приведенную ниже программу на машин- ном языке в программу на языке ассемблера, использовав псев- докоманды ORG, END и DFS, а также двухбайтовую перемен- ную AD: 0888 А9 01 088А 8D F1 08 , 088D А9 00 088F 8D F0 08 >}с 3. Напишите предложения машинного языка, эквивалентные предложению Бейсика J = 100. Машинная программа должна с начинаться с адреса 0840 и содержать обращение к двухбайто- вой переменной J, расположенной по адресам 0858 и 0859 (с пе- f' реставленными, как обычно, байтами). Запишите сначала стар- U шую половину J. Используйте регистр А. "i __________________________________ и Статья 13 Индексированные переменные и индексные регистры Псевдокоманду DFS можно использовать для определения массива (в языке Бейсик массив объявляется предложением DIM). Пусть Т — массив однобайтовых целых чисел от Т(0) до Т(10). Всего здесь 11 чисел [Т(0), Т(1), ... Т(10)], и каждое число — это переменная, занимающая одну ячейку памяти. Все эти переменные можно описать следующим образом: Т DFS !11 4—589
50 Статья 13 На рис. 13.1 показаны адреса всех И переменных в предпо- ложении, что адрес Т(0) равен 0858. Адрес массива Т тоже равен 0858, т. е. совпадает с адресом Т(0). Это иллюстрирует общее правило, по которому адрес величины, хранящейся в бо- лее чем одном байте, всегда совпадает с адресом ее первого .байта. Переменная Адрес Т(0) 0858 (=0858+0) Т(1) 0859 (=0858+1) Т(2) 085А (=0858+2) Т(3) 085В (=0858 + 3) Т(4) 085С (=0858+4) Т(5) 085D (=0858 + 5) Т(6) 085Е (=0858+6) Т(7) 085F (=0858+7) Т(8) 0860 (=0858+8) Т(9) 0861 (=0858+9) Т(10) 0862 (=0858+10) Рис. 13.1. Адреса элементов массива. В последней строчке к шестнадцатерич- ному адресу 0858 прибавляется десятичное число 10. Из рис. 13.1 видно, что для нахождения адреса T(ft) для лю- бого k от 0 до 10 можно воспользоваться выражением 0858+&. В общем случае адрес Т(&) находится как сумма k и адреса Т(0). Этим правилом можно воспользоваться для указания эле- мента массива с помощью индекса-константы. Для того чтобы выполнить предложение Бейсика L=T(6), мы должны напи- сать LDA Т+!6 STA L (сравните с выполнением предложения L=K в статье 8). Здесь мы опять использовали адресное выражение. Если адрес Т ра- вен 0858, то Т+!6 обозначает переменную с адресом 0858-|-6, т. е. 085Е. Из рис. 13.1 видно, что это действительно Т(6). Не лишне еще раз напомнить, что LDA Т+!6 не загружает ре- гистр А значением Т+6. Для этого надо выполнить LDA Т, а затем прибавить 6, используя команду сложения. Такие опера- ции будут рассмотрены в статье 15.
Индексированные переменные и индексные регистры 5Г Нашим правилом можно воспользоваться и в том случае, когда индекс является переменной. Пусть надо выполнить пред- ложение Бейсика L=T(J) (вместо L=T(6)). Нам придется^ воспользоваться следующей последовательностью операций: 1. Загружаем индекс, в данном случае J в индексный ре- гистр. В процессоре 6502 индексными регистрами являются ре.- гистры X и Y. 2. Загружаем T(J) в регистр А с помощью индексной коман- ды. Индексные команды процессора 6502 содержат в качестве операндов v и X или v и Y (и — имя массива), а не просто имя переменной v. 3. Записываем результат в переменную L, как и раньше. Такая программа с использованием регистра Y будет выгля- деть следующим образом: LDY J LDA Т, Y STA L Команда LDA Т, Y загружает регистр А переменной, располо- женной по адресу 0858-{-ft, причем k находится в регистре Y. Как мы уже видели, это адрес переменной T(J). Команда LDA Т, Y в общем случае прибавляет к адресу переменной Т содер- жимое регистра Y без знака, в результате чего образуется эф- фективный адрес; затем содержимое ячейки с этим адресом за- гружается в регистр А. Для выполнения операции T(J)=L можно воспользоваться и вторым индексным регистром — реги- стром X1); LDX J LDA L STA Т, X Для обращения к элементам массива можно также исполь- зовать команды LDX и, Y и LDY v, X (но не LDX v, X или LDY v, Y); здесь v — имя массива. Однако команды STX и STY нельзя использовать с операндами v, X или v, Y (кроме специ- ального случая, который будет рассмотрен в статье 74). , Все рассмотренные нами команды можно разбить на пять, классов, как это иллюстрируют следующие примеры: LDA Q,X Индексация с помощью регистра X LDA Q,Y Индексация с помощью регистра Y LDA Q Отсутствие индексации LDA фп Константа INX Отсутствие адреса или константы » Наименование регистра X происходит от англ. indeX — индекс.— Прим, перев. 4*
52 Статья 13 Это пять примеров способов адресации. В действительности про- цессор 6502 допускает гораздо больше различных способов ад- ресации; все они приведены в табл. П8. Не все способы адреса- ции пригодны для любой команды; в табл. П4 показано, какие способы адресации могут использоваться с каждой командой. Код мнемоники и способ адресации, вместе взятые, опреде- ляют код операции на машинном языке. Так, например, если переменная расположена по адресу 0890, то имеет место сле- дующее соответствие между строками языка ассемблера и ма- шинного языка: STA Т 8D 90 08 STA Т, X 9D 90 08 STA Т, Y 99 90 08 Заметьте, что адрес в строках машинного языка не изменяет- ся— строки отличаются только кодами операций (см. также табл. П4). То обстоятельство, что регистры X и Y могут содержать лишь числа от 0 до 255 (поскольку они имеют по 8 разрядов каж- дый), определяет максимальный размер массивов, с которыми может работать процессор 6502: в большинстве случаев массив не может иметь более 256 элементов. Массив, содержащий бо- лее 256 элементов, называется длинным массивом; техника ра- боты с длинными массивами будет описана в статье 77. Упражнения 1. Напишите строки языка ассемблера для каждого из сле- дующих предложений Бейсика (можете опустить ORG, END и DFS). Не забудьте, что использование индексов-констант и ин- дексов-переменных приводит к разным программным строкам на языке ассемблера, как это было описано выше. (Вообще го- воря, индекс-константу можно рассматривать как частный слу- чай индекса-переменной. Однако это не рекомендуется, так как приводит к необходимости использовать каждый раз дополни- тельную команду). а) 1=Т(К); * б) T(6)=J;b) T(N)=T(7). 2. Напишите предложения Бейсика, эквивалентные приве- денным ниже последовательностям команд на языке ассемб- лера: * a) LDX J LDA Т,Х STA U, X
Система счисления с основанием 256 53 б) * в) LDA Т+11 STA N LDX М DEC U, X 3. Напишите программу на машинном языке, эквивалентную предложению Бейсика Т(5)=16. Пусть описание Т соответст- вует рис. 13.1, и программа начинается с адреса 08А8. Исполь- зуйте регистр А. Постарайтесь сделать программу как можно короче. Статья 14 Система счисления с основанием 256 Мы научились работать с целым рядом систем счисления: двоичной (с основанием 2), шестнадцатеричной (с основанием 16) и десятичной (с основанием 10). Часто используется также восьмеричная система (с основанием 8), но нам она не понадо- бится. Есть, однако, еще одна полезная для нас система, имею- щая основание 256. Мы, разумеется, не собираемся использовать 256 различных символов для цифр этой системы; мы не найдем такого коли- чества символов на клавиатуре ЭВМ. Вместо этого мы будем рассматривать две шестнадцатеричные цифры, взятые вместе, как одну цифру системы с основанием 256. Сложение и вычитание многоразрядных чисел в этой систе- ме осуществляется так же, как и в десятичной, если 10 заменить на 256. Мы видели, что это общее свойство всех систем счисле- ния с фиксированными основаниями. Рассмотрим сложение. В статье 5 мы складывали два четырехразрядных шестнадцате- ричных числа: В785 + 2С84 Е409
54 Статья 14 Сложим теперь те же два числа, считая их двухразрядными числами в системе с основанием 256: (В7) (85) + (2С) (84) (Е4) (09) Одноразрядное число (85) плюс одноразрядное число (84) рав- но двухразрядному числу (01) (09); записываем (09) и пере- носим (01). Одноразрядное число (В7) плюс одноразрядное число (2С), плюс 1 равно одноразрядному числу (Е4). В статье 5 мы также находили разность двух четырехразряд- ных шестнадцатеричных чисел: 834Е - 2Е5А 54F4 Считая эти числа двухразрядными числами в системе с основа- нием 256 и произведя вычитание, получим (83) (4Е) - (2Е) (5А) (54) (F4) При нахождении разности младших разрядов мы должны за- нять 1. Двухразрядное число (01) (4Е) минус одноразрядное число (5А) равно одноразрядному числу (F4). В старшем раз- ряде получаем: одноразрядное число (83) минус одноразрядное число (2Е), минус 1 равно одноразрядному числу (54). Система с основанием 256 важна для нас потому, что каж- дая ее цифра записывается в одном байте. Пусть нам надо сло- жить два 16-разрядных (двухбайтовых) числа: а Ъ + с d е f Здесь a, b, с, d, е и f — цифры системы с основанием 256. Про- цедура сложения выглядит таким образом: 1) складываем b и d, чтобы получить Л и фиксируем пе- ренос; 2) складываем а, с и перенос (если он был) и получаем е. Вычитание выполняется аналогично. Разность а Ь — cd е f
Сложение в процессоре 6502 55 находится следующим образом: 1) вычитаем d из Ь, чтобы получить f, и замечаем, нужно ли было занимать 1; 2) вычитаем с из а и вычитаем еще 1, если был заем; полу- чаем е. Упражнения 1. Выполните следующие действия с двухразрядными числа- ми в системе с основанием 256: * а) (5А) (СЗ) б) (А2) (38) + (ЗА) (2F) - (45) (1Е) (Не забудьте, что, например, (5А) (СЗ) и 5АСЗ — совершенно различные числа: первое — двухразрядное, а второе — четырех- разрядное.) 2. Каков должен быть порядок сложения двух 24-разрядных (трехбайтовых) чисел: а Ь с + def g h i 3. Каков должен быть порядок получения разности двух 32- разрядных (четырехбайтовых) чисел: abed - е f g К i j k I Статья 15 Сложение в процессоре 6502 Теперь мы рассмотрим сложение 8-разрядных и 16-разряд- ных чисел в процессоре 6502, при этом 8-разрядное сложение должно рассматриваться как частный случай 16-разрядного.
Статья 15 Г'т-— 56 Мы знаем, что регистры X и Y называются индексными. Ре- гистр А называется аккумулятором (отсюда и название «ре- гистр А»). Именно в нем накапливается (аккумулируется) сум- ма. В процессоре 6502 сложение можно выполнять только в ре- гистре А (кроме особого случая прибавления 1, рассмотренного в статье 9). В большинстве процессоров предусмотрена команда «при- бавить Q», которая прибавляет Q к содержимому аккумулятора. Если нам надо выполнить операцию L=J+K, то последователь- ность действий будет следующая: 1) загружаем J в аккумулятор; 2) прибавляем К (теперь в аккумуляторе J+K); 3) записываем результат в L (теперь новое значение L рав- но J+K). Пусть теперь J, К и L—16-разрядные числа, занимающие по два байта каждое (с переставленными байтами). Произве- дем сложение в соответствии с процедурой, описанной в пре- дыдущей статье: 1) загружаем J (младшее полуслово); 2) прибавляем К (младшее полуслово и фиксируем пере- нос) ; 3) записываем результат в L (в младшее полуслово); 4) загружаем J-H1 (старшее полуслово); 5) прибавляем К-Н1 и прибавляем 1, если перед этим имел место перенос (это действие для краткости называют сложени- ем с переносом); 6) записываем результат в L+11 (в старшее полуслово). В процессоре 6502 признак переноса хранится в одноразряд- ном регистре, который называется флажком переноса Так же как и регистры А, X или Y, флажок переноса может быть загру- жен константой (1 или 0). Загрузка нулем называется сбросом (или очисткой) и предусмотрена специальная команда CLC (Clear carry — сброс флажка переноса), которая загружает О в разряд переноса (точно так же действие команды LDA ф!0 часто называют очисткой регистра А; если дальше стоит коман- да STA Q, то говорят, что ячейка Q очищена2). Загрузка 1 (в 1-разрядный регистр) называется установкой регистра, и преду- смотрена специальная команда SEC (Set carry — установка флажка переноса), которая загружает 1 в разряд переноса. О Регистр переноса входит в качестве одного из разрядов в регистр со- стояния, в котором, кроме признака переноса, хранятся также и другие при- знаки состояния процессора — переполнения, нуля и т. д., для каждого из которых выделен отдельный разряд (см. статью 67). Потому чаще говорят не о регистре переноса, а о разряде переноса. — Прим, перев. 2> При рассмотрении аппаратной части ЭВМ очистку (clearing) часто на- зывают установкой в исходное состояние (resetting).
Сложение в процессоре 6502 57 Команда «сложение с переносом» в процессоре 6502 имеет мнемонику ADC. Эта команда осуществляет сложение в регист- ре А и, кроме того, прибавляет число из разряда переноса (0 или 1). При этом, если результат меньше 256, команда сбрасы- вает флажок переноса; в противном случае она устанавливает этот флажок. В частности, команда ADC К+П выполняет шаг 5 из описанной выше процедуры; она прибавляет старшую по- ловину К, а также еще 1, если флажок переноса установлен. Команда ADC является единственной командой сложения процессора 6502 (кроме, как отмечалось выше, специального случая прибавления 1). Для того чтобы прибавить без переноса (как, например, в шаге 2), надо сначала сбросить флажок пере- носа командой CLC. Затем мы выполняем операцию «сложение с переносом», но флажок переноса сброшен, так что ADC при- бавляет 0, что не меняет результата. Полная программа 16-разрядного сложения выглядит сле- дующим образом: LDA J CLC ADC К STA L LDA J+I1 ADC К+П STA L-H1 Обычное (8-разрядное) сложение, L=J+K, представлено первыми четырьмя командами этой программы: LDA J CLC ADC К STA L Предположим теперь, что мы хотим сложить три 8-разряд- ных числа без знака, скажем I, J и К. Мы загружаем I, сбра- сываем флажок переноса и прибавляем J. После этого флажок переноса должен остаться сброшенным, так что нет необходи- мости сбрасывать его снова перед прибавлением К (если фла- жок переноса оказался установленным, это означает, что I+J^256, так что результат в любом случае будет ошибоч- ным). В итоге программа вычисления L = I + J+K выглядит следующим образом: LDA I CLC
58 Статья 15 ADC J ADC К STA L Так же как мы раньше загружали константу, мы можем при- бавить константу. Так, операцию L=J-|-20 можно выполнить следующим образом: LDA J CLC ADC #!20 STA L О команде типа ADC часто говорят, что она дает 9-разряд- ный результат, где 8 разрядам в регистре А предшествует раз- ряд переноса. Так, С0+С0=180 (шестнадцатеричное), и, скла- дывая СО с СО с помощью команды ADC, мы получим 1 в раз- ряде переноса и 80 (шестнадцатеричное) в регистре А. В общем случае, если результат сложения й>;256, ADC загрузит k—256 в регистр А и установит разряд переноса. Упражнения 1. Напишите программы на машинном языке для каждого из следующих предложений Бейсика (можете опустить ORG, END и DFS): * a) J =К5+К6 б) S=T(1)+T(2)+T(3) * в) T(J)=U(J)+5 (Замечание: в этом и всех подобных упражнениях считайте все переменные в предложениях Бейсика 8-разрядными, если не оговорено противное.) 2. Напишите предложения Бейсика, эквивалентные приведен- ным ниже последовательностям команд на языке ассемблера: а) LDA LDX CLC ADC STA К J Т, X Т, X * б) LDA N1 CLC ADC #$20 STA W
Связь между переносом и займом 59 В) LDA #!20 LDY S CLC ADC Р4, Y ADC Р5 STA В 3. Напишите программу на языке ассемблера (опустив ORG, END и DFS) сложения двух 24-разрядных величин VI и V2 с получением 24-разрядной величины V3. Считайте, что все три байта каждой величины VI, V2 и V3 хранятся в памяти, начиная с самого правого байта. Статья 16 Связь между переносом и займом При вычитании флажок переноса используется как флажок займа. Чтобы понять, как выполняется заем в процессоре 6502, мы должны рассмотреть связь займа и переноса. Когда мы занимаем, вычитая цифру b из цифры а? Мы за- нимаем, если b больше а (или а<.Ь), так что, если бы мы вы- чли b из а, результат был бы отрицательным. Теперь предста- вим, что мы рассматриваем а—b не как вычитание, а как сло- жение с отрицательным Ь, т. е. а+( — Ь). Как и любое сложе- ние, это сложение может привести к образованию переноса. В каком случае это произойдет? Вспомним, что в системе представления дополнением до двух отрицательное число — b записывается в виде 256—6. Склады- вая а+( — 6), мы фактически выполняем действие а+256—Ь. Это приводит к появлению переноса, если результат слишком велик, чтобы поместиться в байте, т. е. если результат больше 255, или, по-другому, больше или равен 256. Таким образом, условие возникновения переноса запишется так: а+256-6^256.
60 Статья 16 Вычитая 256 из обеих частей неравенства, получим а—Ь^О или а^Ь. Теперь можно отметить любопытный факт: условие возникнове- ния переноса в точности совпадает с условием отсутствия займа при вычитании Ь из а. Это фундаментальное соотношение меж- ду переносом и займом: заем представляет собой инверсию пе- реноса. Если при сложении a-f-(—b) возникает перенос, при вычитании а—Ь не возникает займа, и наоборот. Все это нужно знать для того, чтобы понять действие коман- ды SBC (Subtract with carry — вычитание с переносом). Как и ADC, SBC выполняет операцию вычитания в регистре А, и, по- добно сложению, вычитание в процессоре 6502 выполняется только в этом регистре (за исключением особого случая вычи- тания 1). Так же как команда ADC Q прибавляет Q к содержимому регистра А, команда SBC Q вычитает Q из содержимого реги- стра А. Фактически вычитание выполняется путем прибавления — Q к содержимому регистра А; это упрощает аппаратуру про- цессора. Перенос, возникающий при этом сложении, устанавли- вает разряд переноса. Команда ADC Q дополнительно прибавляет 1, если флажок переноса был установлен (содержал 1). Команда SBC Q допол- нительно вычитает 1, если флажок переноса был сброшен (со- держал 0), потому что в этом случае как раз имел место заем. Другими словами, можно сказать, что ADC Q прибавляет число (0 или 1) из разряда переноса. Точно так же SBC Q вы- читает инверсию числа, находящегося в разряде переноса. Следует обратить внимание на то, что команды инкремента и декремента (INC, DEC, INX, DEX, INY, DEY) не влияют на состояние флажка переноса. Так, если регистр X содержит чис- ло 255, прибавление к нему 1 командой INX не установит фла- жок переноса. В отличие от этого прибавление 1 к содержимому регистра А с помощью команд CLC и ADC *!1 установит фла- жок переноса, если в регистре А было число 255. Точно так же загрузка регистров и запись в память не влия- ют на состояние флажка переноса. Например, в программах предыдущей статьи можно было во всех случаях поменять мес- тами две первые команды, поставив CLC перед LDA, потому что LDA не влияет на состояние флажка переноса, т. е. оставляет его сброшенным. Рассмотрим теперь несколько примеров сложения и вычи- тания, обращая внимание на установку разряда переноса:
Связь между переносом, и займом 61 1) А=3, в разряде переноса О, ADC ф!5. Результат в реги- стре А равен 8 (=3+5). Разряд переноса остается сброшенным, так как результат меньше 256. 2) А=5, в разряде переноса 1, ADC #!10. Результат равен- 16 (=5+10, плюс 1 из-за переноса). Разряд переноса сбрасы- вается. 3) А=200, в разряде переноса 0, ADC ф!200. Результат равен 144 (т. е. 200+200—256). Разряд переноса устанавлива- ется, поскольку 200+200 больше 255. 4) А=255, в разряде переноса 1, ADC +!0. Результат ра- вен 0 (т. е. 255+0, плюс 1 из-за переноса минус 256). Разряд, переноса остается установленным, поскольку первоначальный результат (255+0+1) больше 255. 5) А=9, в разряде переноса 1, SBC +|=!3. Результат в реги- стре А равен 6 (=9—3). Разряд переноса остается установлен- ным, поскольку здесь нет условия для займа — результат не меньше 0. 6) А=10, в разряде переноса 1, SBC ф!20. Результат ра- вен 246 ( = 10—20+256). Разряд переноса сбрасывается, по- скольку имеет место условие для займа: 10—20 меньше 0. 7) А=15, в разряде переноса 0, SBC #!5. Результат равен' 9 (15—5, минус еще 1 в силу признака займа, поскольку разряд переноса сброшен). Разряд переноса устанавливается. 8) А=30, в разряде переноса 0, SBC 4Н30. Результат ра- вен 255 (т. е. 30—30, минус еще 1 из-за признака займа плюс 256). Разряд переноса остается сброшенным. Можно заметить, что команда SBC, производя заем, всегда занимает 256; другими словами, она вычитает (256+/?)—q, если данная разность р—q дает отрицательный результат. Аналогич- но если результат сложения больше 255, то команда уменьшает его на 256. Упражнения 1. Рассмотрите следующие примеры. В каких случаях задан- ная операция вычитания потребует займа? В каждом случае по- чему заем требуется или не требуется? * а) 4-3; * б) 4—4; * в) 4—5. 2. Для каждого из вышеприведенных примеров выразите вычитание как сложение с отрицательным значением вычитае- мого, записанного в одном байте в форме дополнения до двух. Возникает ли в этом случае перенос? Почему возникает или по- чему не возникает? 3. + а) Предположим, что в регистре А содержится 5, а. флажок переноса сброшен. Что будет содержаться в регистре А. после выполнения команды SBC =ОД=!3? В каком состоянии бу- дет флажок переноса?
62 Статья 17 б) Ответьте на оба вопроса п. За в предположении, что перед выполнением команды SBC *13 флажок переноса был установлен, а не сброшен. Статья 17 Вычитание в процессоре 6502 Как и в случае сложения, мы рассмотрим вычитание для 8- и 16-разрядных чисел. Пусть мы хотим выполнить операцию L=J —К, причем все эти числа 8-разрядные. Порядок действий -будет следующим: 1) загружаем J (заносим J в аккумулятор); 2) вычитаем К (теперь в аккумуляторе J—К); 3) записываем результат по адресу L (теперь новое значе- ние L равно J — К). Если J, К и L представляют собой 16-разрядные числа, за- нимающие в памяти по два байта каждое (с переставленными -байтами), порядок вычислений, согласно правилам, рассмотрен- ным в статье 14, будет следующим: 1) загружаем! (младшее полуслово); 2) вычитаем К (младшее полуслово) и определяем состояние флажка переноса, как это описывалось в предыдущей статье, с тем, чтобы зафиксировать наличие или отсутствие займа; 3) записываем результат в L (младшее полуслово); 4) загружаем J+I1 (старшее полуслово); 5) вычитаем К+!1 и еще 1, если перед этим был выполнен заем (другими словами, вычитаем К~Н1 с переносом); 6) записываем результат в L-f-!l (старшее полуслово). Команда SBC К+П реализует шаг 5. Команда вычитает старшее полуслово К из содержимого регистра А и вычитает еще 1, если флажок переноса был сброшен (что указывает на имевший место заем). Если результат отрицателен, SBC сбра- сывает флажок переноса; в противном случае SBC устанавли- вает этот флажок. Команда SBC является единственной командой вычитания ^процессора 6502 (кроме особого случая вычитания 1). Если мы
Вычитание в процессоре 6502 63 хотим вычесть без займа (как, например, в шаге 2), то сначала надо установить флажок переноса командой SEC. (Всегда пом- ните о необходимости сбросить флажок переноса перед сложе- нием без переноса и установить флажок переноса перед вычи- танием без займа.) Полная программа 16-разрядного вычитания выглядит сле- дующим образом: LDA J SEC SBC К STA L LDA J-Hl SBC K+!l STA L4-!l Обычное 8-разрядное вычитание, L=J—К, представлен», первыми четырьмя командами этой программы: LDA J SEC SBC К STA L Можно также вычитать константы; так, операцию L=J—20 можно выполнить следующим образом: LDA J SEC SBC =Н=!20 STA L Чтобы выполнить L = — J, можно заменить эту операцию на' L=0 —J: LDA #10 SEC SBC J STA L Заметьте, что команда SEC, как и CLC, может быть распо- ложена в начале такой программы, так как LDA не влияет на> состояние флажка переноса (т. е. оставляет его в данном слу- чае установленным). Точно так же как при вычислении I+J+K мы могли не сбра- сывать флажок переноса после прибавления J, так и при вы-
€4 Статья 17 числении I—J — К можно не устанавливать флажок переноса после вычитания J. Если I—J не отрицательно, флажок пере- носа установится сам по себе. Между прочим, если I—J не отрицательно, мы можем вычислить I—J-J-K необычным обра- зом: загрузить I, сбросить флажок переноса (!), вычесть J и за- тем прибавить К, не сбрасывая флажка переноса. Фактически в этом случае вычитается J-J-1, а затем прибавляется К+1, так что результат получается правильный. Точно так же, если I+J не больше 255, мы можем вычислить I+J —К следующим образом: загрузить I, установить флажок переноса, прибавить J и вычесть К, не устанавливая флажок переноса. Фактически здесь вычисляется I+(J+1) — (К+1). Упражнения 1. Напишите программы на языке ассемблера для каждого из следующих предложений Бейсика (опустив ORG, END и DFS): а) Т=РЗ—Р4 * б) S9=P+9-R(L) в) U=-V(8) 2. Напишите предложения Бейсика, эквивалентные приве- .денным ниже последовательностям команд на языке ассеблера: * a) LDX J LDA Т, X SEC SBC Т+!3 STA Н б) LDA N SEC SBC М+15 STA L DEC L * в) LDA #$A0 LDX I CLC ADC T, X SEC SBC U, X STA U, X
Команды пересылки и комментарии 65 3. Напишите программу на языке ассемблера (опустив ORG, END и DFS) вычитания двух 32-разрядных переменных ХА и ХВ с получением 32-разрядного результата ХС. Считайте, что все 4 байта каждой переменной ХА, ХВ и ХС хранятся в памя- ти, начиная с самого правого байта. Статья 18 Команды пересылки и комментарии Мы уже выяснили, что в процессоре 6502 не предусмотрена непосредственная пересылка чисел из одной ячейки в другую. Можно только переслать число из ячейки в регистр (загрузка) или из регистра в ячейку (запись). Мы можем также переслать число из одного регистра в другой. Это делается с помощью команд пересылки. Наиболее важными командами пересылки в процессоре 6502 являются: ТАХ Пересылка из регистра А в регистр X TAY Пересылка из регистра А в регистр Y ТХА Пересылка из регистра X в регистр А TYA Пересылка из регистра Y в регистр А Заметьте, что мы не можем выполнить пересылку непосредст- венно из регистра X в регистр Y, или наоборот. Команды пересылки часто используются, если требуется за- грузить два регистра одной и той же константой. Вместо того чтобы писать, например, LDA #!1 LDY #11 можно написать LDA #!1 TAY 5—589
66 Статья 18 В статье 10 говорилось, что команда, состоящая из одной мне- моники, как, например, TAY, занимает 1 байт, в то время как коды команд LDA *11 и LDY *11 занимают по 2 байта каж- дый.- Таким образом, используя 3 байта вместо 4, мы экономим память. Иногда приходится пересылать данные из одного регистра в другой в силу того, что различные регистры выполняют различ- ные функции. Так, например, чтобы выполнить L=T(J-|-K), следует написать: LDA J CLC ADC К ТАХ LDA Т, X STA L Заметьте, что вычисление J+K должно производиться в регист- ре А (со сбросом флажка переноса, как было показано в статье 15), а затем результат следует переслать в индексный регистр (в данном случае X), чтобы использовать его в индексной команде (LDA Т, X). Заметим, что ТХА нельзя заменить на LDA X. Чтобы по- нять, почему это так, рассмотрим преобразование в строки ма- шинного языка. Когда мы пишем LDA Q, мы помещаем адрес Q во второй и третий байты машинной программы; но не суще- ствует адреса X, потому что адреса присущи только ячейкам, а X — не ячейка, а регистр (команда LDA X воспринимается про- цессором Эпл, но при этом предполагается, что X — это имя пе- ременной, а не регистр X). При пересылке данных из одного регистра в другой содер- жимое первого регистра не изменяется, и после пересылки одно и то же число будет в обоих регистрах. Команды пересылки в этом отношении напоминают команды загрузки и записи в па- мять. Любая команда языка ассемблера может сопровождаться комментарием, который начинается после пробела, за которым следует точка с запятой. Так, приведенную выше программу можно написать следующим образом: LDA J ; Загрузить J в регистр А CLC ; Прибавить К, получив в регистре А ADC К ; J + K (сначала сбросить флажок переноса) ТАХ ; Загрузить J + К в регистр X, чтобы LDA Т, X ; можно было извлечь Т (J-J-K) STA L ; и записать в L
Команды пересылки и комментарии 67 Пробелы после точки с запятой не обязательны. Строка может начинаться с символа точки с запятой, в этом случае строка рассматривается как комментарий. Комментарии в языке ассемблера выполняют ту же функ- цию, что и в Бейсике: с их помощью можно пояснить самому себе (или тому, кто будет работать с программой), что делается в конкретных строках. Однако программа на языке ассемблера гораздо менее наглядна, чем на Бейсике, поэтому рекомендует- ся каждую строку программы сопровождать комментарием. Упражнения 1. Напишите программы на языке ассемблера для каждого из следующих предложений Бейсика (опустите ORG, END и DFS): * a) P1(I+J)=5 б) К=Т4(1-М) * в) J=N(N(J)) 2. Напишите предложения Бейсика, эквивалентные приве- денным ниже последовательностям команд на языке ассемб- лера: а) LDA ТАХ STA J Т, X * б) LDY INY TYA CLC ADC STA V и W в) LDA CLC ADC ТАХ LDA STA Т+!3 Т+14 Т, X R 3. Две программы эквивалентны, если они выполняют те же вычисления. Например, две следующие программы эквива- лентны: 5*
68 Статья 19 LDA М LDA M SEC CLC SBC #!8 ADC #$F8 STA N STA N поскольку обе они вычисляют N=M—8 (в шестнадцатеричной системе —8 соответствует F8). Для каждого из приведенных ни- же примеров постройте эквивалентную в вышеуказанном смыс- ле, но более короткую программу (с меньшим числом байтов, занятых кодами команд). Такую операцию называют оптимиза- цией программы: * a) LDA LDY STA C C U, Y 6) LDX #!5 LDA T, X TAX * в) LDX V INX STX V Статья 19 Переходы и метки Команда перехода в процессоре 6502 осуществляет переход на некоторое место в программе, если выполняется некоторое условие1). Это напоминает конструкцию IF b THEN п в Бейси- В английском языке существуют два термина — branch (ветвление) и jump (прыжок, переход), — которые обозначают передачу управления (пе- реход) в программе. Четкого разделения функций между этими понятиями нет. Обычно branch обозначает переход на некоторое количество байтов впе- ред или назад относительно адреса данной команды, в то время как операн- дом команд jump служит сам адрес перехода. Поскольку в русском техни- ческом языке нет удачного эквивалента слову branch, мы будем всюду в дальнейшем (за исключением статьи 27) пользоваться термином переход.—> Прим, перев.
Переходы и метки 69 ке, где b — условие, ап — номер строки. Предложение выполня- ет переход на строку с номером п, если b имеет место; в против- ном случае управление передается на следующую строку в про- грамме. В процессоре 6502 предусмотрены две команды перехода, анализирующие состояние флажка переноса: ВСС L Переход на метку L, если флажок переноса сброшен BCS L Переход на метку L, если флажок переноса установлен Здесь ВСС обозначает branch on carry clear (переход, если флажок переноса сброшен), a BCS — Branch on carry set (пере- ход, если флажок переноса установлен). В отличие от Бейсика в языке ассемблера у строк отсутству- ют номера. Взамен имеются метки предложений, или просто метки, которые по смыслу аналогичны номерам строк, но с дву- мя существенными отличиями. Во-первых, не каждое предложе- ние имеет метку. В Бейсике каждое предложение сопровожда- ется номером строки, а в языке ассемблера метки обычно име- ют только те предложения, которые в них нуждаются, например для осуществления перехода. Другое отличие заключается в том, что метки — это не чис- ла. Меткой может быть буква, например, L, но может быть и более сложное образование. В LISA для меток существуют сле- дующие правила: 1) метка должна начинаться с буквы (верхнего регистра); 2) метка может состоять из одной буквы, или любого коли- чества символов вплоть до (и не более чем) 6 (LISA 1.5) или 8 (LISA 2,5) 0; 3) остальные символы в метке могут включать буквы, циф- ры и некоторые специальные символы (об этом будет идти речь ниже). Таким образом, у вас широкие возможности выбора меток. Вот некоторые примеры: I HEAT DOG HAND LOVE C6502 J LENGTH CAT FOOT HATE C6502A W START HIPPO NOSE ENVY A2B3C4 Z NEXT RHINO EAR PRIDE DARN В2 LAST BOSTON CARTER GOGOGO HECK Н4 DONE MIAMI REAGAN STOPPP GOSH *) В ассемблере LISA 1.5 более длинные метки не отмечаются как ошиб- ки, но используются только их первые 6 символов. Не рекомендуется ис- пользовать эту возможность LISA, так как это может привести к путанице, например, метки MULTIPLICAND и MULTIPLIER рассматриваются как одна и та же метка (MULTIP).
70 Статья 19 Ассемблер LISA 1.5 разрешает использовать довольно мно- го специальных символов в составе меток, но автор этого не ре- комендует. В LISA 1.5 можно, например, образовать метки U = =V, или I^J> или даже X’ но длинные программы обыч- но достаточно запутаны и без этих усложнений. LISA 2.5 раз- решает использовать только специальные символы . (точка) и - (дефис). Те же правила относятся к образованию имен переменных и массивов в программах на языке ассемблера. Имя перемен- ной или массива — это просто метка. Помните, что нельзя ис- пользовать слишком длинные метки (вроде BALTIMORE) и метки, начинающиеся с цифры (как 4SCORE). Любая метка начинается с самого4 начала строки, с пози- ции 1. Если команда не имеет метки, она должна начинаться после по крайней мере одного пробела (позиция 1 должна со- держать пробел). Команды ВСС и BCS можно использовать для анализа оши- бочных ситуаций. Если мы хотим перейти на метку ERROR при выполнении условия I-|-J^256, мы должны написать LDA I ; Загрузить I CLC ; Сбросить флажок переноса ADC J ; Вычислить I+J, если BCS ERROR ; результат ^256, перейти на ERROR Такой способ анализа признака переноса полезен в случае прибавления 8-разрядной величины без знака R к 16-разрядной величине Q. Конечно, можно рассматривать R как 2-байтовую величину с нулевым старшим полусловом; тогда, сложив стар- шие полуслова, мы должны загрузить старшее полуслово Q, прибавить 0 (с переносом) и записать результат назад в стар- шее полуслово Q. Но это равносильно прибавлению 1 к величи- не Q при условии установленного флажка переноса, на что требуется только две команды, а не три. Если наша 16-разряд- ная величина хранится в Q и Q+U с переставленными, как обычно, байтами, полная последовательность действий будет следующей: LDA Q ; Прибавить младшее полуслово Q CLC ; (сбросив сначала флажок переноса) ADC R ; к 8-разрядному числу R STA Q ; и записать результат назад ВСС Z4 ; Если флажок переноса был установлен, то INC Q + 11 ; прибавить 1 к старшему полуслову Q Z4 (следующая команда)
Переходы и метки 71 Точно так же, если мы вычитаем R из Q, мы можем загру- зить старшее полуслово Q, вычесть 0 (с помощью SBC) и запи- сать результат в Q; но это равносильно вычитанию 1 из Q при условии сброшенного флажка переноса (что говорит об имев- шем место займе), и опять мы экономим одну команду. Полная последовательность действий будет следующая: LDA Q ; Вычесть 8-разрядное число SEC ; R (установив сначала флажок переноса) SBC R ; из младшего полуслова Q STA Q ; и записать результат назад BCS Z7 ; Если флажок переноса был сброшен, вычесть DEC Q + !1 ; 1 из старшего полуслова Z7 (следующая команда) Упражнения 1. Какие из приведенных ниже меток допустимо использо- вать в LISA? Если метку нельзя или не следует использовать, объясните, почему: а) V * б) CHECKRANGE5 в) 6502С * г) CMPUTR д) JPLUSK 2. Напишите программы на языке ассемблера, эквивалент- ные следующим предложениям Бейсика, организовав переход на ERROR, если на какой-либо стадии вычислительного процесса результат оказывается больше 255: * а) М=Т14-Т2+ТЗ б) C=D(I+J) * в) T(K+L)=R+4 3. .Какое предложение Бейсика соответствует приведенной последовательности команд языка ассемблера? Считайте, что метка TWENTY соответствует строке с номером 20 на Бейсике: LDA J SEC SBC К ВСС TWENTY
72 Статья 20 Статья 20 Сравнение, флажок нуля и переходы Кроме команд ВСС и BCS, имеется еще много других ко- манд переходов. Две из них, BEQ (branch on equal — переход по равенству) и BNE (branch on not equal — переход по нера- венству), часто используются, если нам надо сравнить два числа. На Бейсике это можно выполнить следующим образом: IF P = Q THEN 200 IF POQ THEN 200 (вспомним, что < > в Бейсике значит =#). В языке ассембле- ра мы используем команды сравнения; в процессоре 6502 пре- дусмотрены три таких команды: CMP v Сравнение содержимого регистра А с v СРХ v Сравнение содержимого регистра X с v CPY v Сравнение содержимого регистра Yea Для того чтобы выполнить приведенные выше предложения Бейсика, надо сделать следующее: 1) загрузить Р в регистр; 2) сравнить содержимое этого регистра с Q; 3) перейти по равенству (или по неравенству). Пусть метка L200 соответствует строке с номером 200. Тогда для первого предложения Бейсика получим LDA Р LDX Р LDY Р CMP Q или СРХ Q или CPY Q BEQ L200 BEQ L200 BEQ L200 Каждая команда сравнения выполняет вычитание, в данном случае Р—Q !). Однако результат вычитания не загружается на- зад в регистр, как это делается командой SBC. Он использует- ся только для сравнения. Если P=Q, тогда Р —Q=0, а это как раз и обнаруживает команда сравнения (наряду с другими ус- ловиями). *> Вычитание всегда выполняется без займа, независимо от состояния флажка переноса. Поэтому перед командой сравнения нет необходимости еспользовать SEC, как мы обычно делаем перед SBC.
Сравнение, флажок нуля и переходы 73 Если результат вычитания равен нулю, то команда сравне- ния устанавливает флажок, который называется флажком (признаком) нуля. Признак нуля хранится в специальном 1-раз- рядном регистре, напоминающем регистр признака переноса. Если результат вычитания не нуль, команда сравнения сбрасы- вает флажок нуля. Команды BEQ и BNE анализируют флажок нуля, который только что был установлен или сброшен, и осуществляют пе- реход: BEQ L Переход на L по равенству (флажок нуля установлен) BNE L Переход на L по неравенству (флажок нуля сброшен) Заметьте, что признак нуля равен нулю (т. е. флажок сбро- шен), если результат сравнения не равен нулю. Можно выполнять сравнение с константой; для условия Р=3 (вместо P=Q) следует написать LDA Р или LDX Р или LDY Р CMP #!3 СРХ #!3 CPY #!3 BEQ L200 BEQ L200 BEQ L200 Можно также сравнить содержимое регистра А (но не X или Y) с индексированной переменной, используя индекс. Так, в сле- дующих строках осуществляется переход на метку DIFFER, если T(J)=#U(J): LDX J LDA T, X CMP U, X BNE DIFFER •.Установить индекс J ;3агрузить T (J) и сравнить его ;с U(J), если равенства нет, ;перейти на DIFFER Обратите внимание на то, что после символов точки с запятой в этом примере нет пробелов; как уже отмечалось, это допуска- ется правилами ассемблера LISA. Рассмотренные команды перехода являются условными: пе- реход осуществляется только при выполнении некоторого усло- вия. В процессоре 6502 предусмотрены также команды безуслов- ных переходов; они осуществляют передачу управления в опре- деленную точку программы в обязательном порядке без всяких условий. Основной командой такого рода является JMPa(Jump to а, переход на а0), которая всегда передает управление намет- *> См. примеч. на стр. 63. — Прим, перев.
74 Статья 20 ку а. Эта команда аналогична GO ТО а Бейсика или Форт- рана1). Начинающие программисты часто делают одну и ту же ошибку: пишут команду перехода, например на метку ALPHA, и в следующей же строке используют метку ALPHA. Такая конструкция: ВСС ALPHA ALPHA (следующая команда) всегда неверна. (Поразмыслите об этом. Если флажок переноса сброшен, вы переходите на ALPHA. В противном случае вы ни- куда не переходите, а выполняете следующую за ВСС команду, которая начинается с метки ALPHA. Другими словами, вы пере- ходите на ALPHA независимо от того, сброшен флажок пере- носа или нет!) Если вы используете в своей программе команду условного перехода, обязательно поставьте вслед за ней что-то, что надо выполнить, если переход не произошел, т. е. если про- веряемое условие оказалось ложным. Упражнения 1. Напишите программы на языке ассемблера, эквивалент- ные следующим предложениям Бейсика (используйте метку TWENTY вместо номера строки 20): * a) IF B+C=D THEN 20 б) IF T(J)+1< >R THEN 20 * в) IF C—T(6)=3 THEN 20 2. Напишите предложения Бейсика, соответствующие при- веденным ниже последовательностям команд на языке ассем- блера (используйте номер строки 20 вместо метки TWENTY): a) LDA W SEC SBC W1 CMP #14 BNE TWENTY * 6) LDY R DEY CPY S BEQ TWENTY l) Разница в значениях терминов «branch» и «jump» специфична для про- цессора 6502; в других ЭВМ эти термины могут использоваться по-другому.
Циклы 75 LDA J CLC ADC К TAX LDA L CMP T, X BNE TWENTY 3. Найдите ошибку в следующей программе на языке ассем- блера, сравнивающей W и T(J): LDY J LDX W СРХ T, Y BEQ TWENTY Статья 21 Циклы Флажок нуля часто используется в циклах. В языке Бейсик цикл обычно организуется с помощью предложения FOR, напри- мер, так: FOR J=1 ТО N (предложения, выполняемые в цикле) NEXT В машинном языке и в языке ассемблера отсутствуют пред- ложение FOR и его эквиваленты, и вместо них используются другие предложения. И в Бейсике мы могли организовать тот же цикл по-другому, написав, например: 10 J = 1 20 (предложения, выполняемые в цикле) 30 J=J+1 40 IF J<=N THEN 20
76 Статья 21 Что-то вроде этого’> можно выполнить и с помощью команд про- цессора 6502, но проверка процессором 6502 условия (J< = N) в строке 40 оказывается непростой задачей, поскольку одновре- менно проверяются два условия, на равенство и на неравенство. Заметьте, что программа 10 J = 1 20 (предложения, выполняемые в цикле) . 30 J=J+1 40 IF JON THEN 20 была бы в нашем случае неверна; цикл выполнится здесь N—1 раз, а не N раз. На последнем шаге цикла J будет равно J— 1; затем в предложении 30 J станет равно N, но тогда предложе- ние 40 не вернет управление на начало цикла. Можно предложить несколько способов решения этой про- блемы. Например, можно проводить проверку условия с числом N-М, а не N: 10 J = 1 20 (предложения, выполняемые в цикле) 30 J=J + 1 40 IF JON + 1 THEN 20 Это тоже нелегко выполнить с помощью команд процессора 6502, если только N не является константой. Другой способ — переставить J=J-f-1 в начало цикла: 10 J=0 20 J=J+1 30 (предложения, выполняемые в цикле) 40 1FJONTHEN20 Заметьте, что в этом случае мы должны начать с J=0, а не с J=l, поскольку при первом же прохождении цикла предложе- ние J=J-f-1 прибавит 1 к 0. Именно так обычно и программируются циклы. Давайте по- местим величину J не в ячейку памяти с именем J, а в регистр X (это называется назначением регистра (register assign- ment)— регистру,X назначается роль переменной J). Если в цикле выполняется единственное предложение T(J) =0, то мож- но написать Если, изучая Бейсик, вы не сталкивались с такими циклами, рассмот- рите этот пример внимательно и убедитесь, что вы понимаете, почему он эк- вивалентен предыдущему.
Циклы 77 •LDX 4Н0 I Установить J=0 ТХА ; Очистить регистр А LOOP INX ; Установить J=J+1 STA Т, X ; Установить T(J)=O СРХ N ; Сравнить J с N BNE LOOP ; Если не равны, повторить Как уже отмечалось в предыдущей статье, команда СРХ уста- новит флажок нуля, если J—N=0 (т. е. если J=N), и сбросит этот флажок, если J#=N. Команда BNE затем осуществит пе- реход, если флажок нуля сброшен, т. е., другими словами, если J#=N, что и требуется. Назначение регистра — весьма полезный прием в програм- мировании на языке ассемблера. Заметьте, что в приведенном примере регистр А также назначается: в него заносится посто- янное значение 0. В этом случае мы не должны перезагружать О в регистр А каждый раз, когда очищается следующая ячейка T(J), и это несколько ускоряет выполнение программы. Разуме- ется, в приведенном примере, кроме программных строк, должна быть еще и секция данных, содержащая по крайней мере стро- ки N DFS !1 и Т DFS п. Используя выражения вида Q, X и Q, Y в командах процес- сора 6502, не следует забывать, что не разрешается писать Q, J или Q, N — за именем массива (например, Q) может следовать только обозначение индексного регистра (X или У). Другое пра- вило— используя индекс-константу, не считайте ее индексом- переменной. Если, например, требуется загрузить в регистр А переменную Т(6), используйте команду LDA T-f-!6. Последова- тельность команд LDX *!6 и LDA Т, X приведет к тем же ре- зультатам, но в этом случае понадобится дополнительное вре- мя, дополнительная команда и дополнительный регистр, кото- рый можно было бы использовать для чего-нибудь другого. За- метьте при этом, что попытка рассматривать индекс-перемен- ную как -индекс-константу не просто неэффективна, а неверна (команда LDA T+N прибавит к Т адрес переменной N, а не ее значение). Упражнения 1. Напишите на Бейсике циклы с предложением FOR, соот- ветствующие каждой из приведенных ниже программ на языке ассемблера. Во всех случаях содержимое регистра X обозна- чается в программе на Бейсике через J:
78 Статья 21 а) LDX #!0 LOOP INX LDA Т, X STA U, X СРХ L BNE LOOP * б) LDX #10 LDA W TR54 INX CMP V, X BEQ EIGHTY CPX #1100 BNE TR54 (Вместо метки EIGHTY используйте номер строки 80.) в) LDX M DEX LDA #!0 CLC ADD INX ADC T, X CPX N BNE ADD (Обозначьте содержимое регистра А через S и поместите пред- ложение S=0 непосредственно перед циклом.) 2. Напишите программы на языке ассемблера, соответствую- щие каждому из приведенных ниже циклов на Бейсике: * a) FOR J = 1 TO M T(J)=J NEXT J (Не забудьте при этом об отсутствии команды STX Т, X в про- цессоре 6502.) 6) K=o FOR M=1 TO 50 IF T(M)=W THEN K==K+1 NEXT M (Храните значение W в регистре А. Организуйте проверку ус- ловия T(M)=W и, если это переменные не равны, переходите
Дополнительные сведения о циклах и флажке нуля 79 вперед на L00P2 *>. Если они равны, выполняйте следующую команду.) * в) FOR 1 = 1 ТО К4-1 Т(1)=Т(1)—1 NEXT I (Используйте вспомогательную переменную КР1 со значением К+1. Позаботьтесь, чтобы ваша программа не изменяла значе- ние К, иначе вы получите нежелательный побочный эффект.) 3. Напишите программу на языке ассемблера, которая запи- сывает нули в Т(2), Т(4), Т(6) и т. д. до Т( 100). Статья 22 Дополнительные сведения о циклах и флажке нуля Важность флажка нуля определяется тем, что он устанав- ливается и сбрасывается не только командой сравнения, но и многими другими, например: Командами загрузки — LDA, LDX, LDY Командами инкремента — INC, INX, INY Командами декремента — DEC, DEX, DEY Командой сложения — ADC Командой вычитания — SBC Командами пересылки — TAX, TAY, ТХА, TYA Как мы увидим дальше, многие другие команды также влияют на состояние флажка нуля. П В некоторых версиях Бейсика предложение IF T(M)=W THEN К=К+1 незаконно. Можно использовать следующий эквивалент: 10 IF T(M)OW THEN 30 20 К=К+1 30 (следующее предложение)
804 Статья 22 При выполнении любой из приведенных команд процессор анализирует результат операции. Если он равен нулю, флажок нуля устанавливается. В противном случае флажок сбрасыва- ется. Так, мы можем перейти на метку NON, если Q не равно ну- лю, следующим образом: LDA Q LDX Q LDY Q BNE NON ИЛИ BNE NON ИЛИ BNE NON Какой бы регистр (А, X или Y) не загружался, флажок нуля установится в случае нулевого результата и сбросится, если ре- зультат не равен нулю. Поэтому команда BNE выполнит пере- ход, если и только если Q не равно нулю. В том случае, когда мы хотим установить Q=R—S, а затем перейти на NON, если Q не равно нулю, мы можем написать LDA R ; Загрузить R SEC ; Не забудьте установить флажок SBC S ; переноса перед вычитанием S STA Q ; Записать (состояния флажков не изменяются) BNE NON ; Если не 0, перейти на NON Будьте внимательны: команда STA (так же как и команды STX и STY) не влияет на состояние флажка нуля В приведенной программе этот разряд устанавливается ко- мандой SBC: если результат вычитания нулевой (т. е. Q=0), флажок нуля устанавливается, в противном случае флажок сбрасывается (и мы переходим на NON). Весьма распространено использование команды BNE после команды декремента в конце цикла. В программах для процес- сора 6502 цикл чаще всего строится следующим образом: 1) загружаем регистр (обычно X или Y) требуемым числом шагов цикла; 2) выполняем один раз вычисления в цикле; 3) выполняем декремент регистра (обычно DEX или DEY); 4) если результат ненулевой, возвращаемся к шагу 2. Вместо регистров X и Y можно использовать ячейку памяти. Пусть она называется COUNT, тогда мы командой DEC COUNT уменьшаем на 1 ее содержимое на шаге 3, а начальное значение записываем на шаге 1. Регистр А для этой цели использовать не рекомендуется, так как для него нет команды декремента (вычесть 1 из содержимого регистра А можно парой команд SEC и SBC #11). О По табл. ПЗ можно определить, какие команды устанавливают фла- жок нуля. Эти команды содержат Z в столбце «Флажки».
Дополнительные сведения о циклах и флажке нуля 81 Приведенная процедура выполняет цикл «задом наперед». В индексный регистр загружается N (число шагов), поэтому первым выполняется N-шаг цикла. Пусть, например, требуется очистить элементы массива от Т(1) до T(N). Используя, как и в предыдущей статье, регистр X для хранения значения J, мож- но написать: LDX N ; Установить J = N LDA #!0 ; Очистить регистр A LOOP STA T, X ; Установить T(J) =0 DEX ; Установить J = J—1 BNE LOOP ; Если J=#0, вернуться Такая программа очистит T(N), затем T(N —1) и т. д. Заметь- те, что Т(0) не очищается. При последнем выполнении цикла в регистре X будет 1; затем выполняется декремент X; после это- го, поскольку в регистре X теперь нуль, мы не возвращаемся на LOOP, а идем дальше, выполняя следующую команду. Как и раньше, в секции данных программы должны быть строки N DFS !1 и Т DFS п. Как правило, анализ равенства переменной нулю произво- дится в любом процессоре быстрее анализа равенства перемен- ной некоторому ненулевому значению. В приведенном примере мы анализировали равенство нулю, и цикл состоял лишь из трех команд — STA, DEX и BNE. В предыдущей статье мы ана- лизировали равенство значению N, и в цикле было четыре ко- манды— INX, STA, СРХ и BNE. Выполнение N раз трех команд займет гораздо меньше времени, чем выполнение N раз четы- рех команд, особенно если N велико. Это общее правило следует учитывать, используя языки про- граммирования более высокого уровня. Если, например, в про- грамме на Бейсике или Фортране имеется переменная, которая может принимать два значения— 1 или 0, и вам надо выделить ситуации, когда она равна 1, сделайте наоборот: выделяйте си- туации, когда она не равна нулю. После того как ваша програм- ма будет преобразована компилятором в машинный язык (если в ЭВМ используется компилятор), полученная машинная про- грамма будет работать несколько быстрее. Заканчивая цикл командами DEX и BNE, не используйте между этими командами какие-либо команды, влияющие на со- стояние флажка нуля. Так, в последовательности DEX — LDY — BNE команда BNE выполнит переход, если в регистре Y (а не X) содержится 0. Вероятность ошибки уменьшается, если ко- манды BEQ или BNE следуют непосредственно (если это толь- ко возможно) за командой, которая устанавливает или сбрасы- вает флажок нуля. 6—589
«2 Статья 23 Упражнения 1. Напишите программу на языке ассемблера, соответствую- щую приведенной ниже программе на Бейсике, не используя жоманды сравнения (СРМ, СРХ или CPY): 10 К=0 20 FOR J = 1 ТО N 30 IF T(J)<>0 THEN 50 40 K=K+1 50 NEXT J Организуйте цикл «задом наперед»: сначала проанализируйте T(N), затем T(N —1) и т. д. Сохраняйте К в регистре Y и за- пишите его в память только в конце программы. 2. Все программы упр. 1 предыдущей статьи выполняются так, что индекс возрастает (поскольку используются команды INX или INY). Измените первые две программы так, чтобы ин- декс уменьшался (использовав DEX или DEY). 3. Как можно оптимизировать приведенную ниже последова- тельность команд (см. упр. 3, статья 18)? Почему? DEY CPY #!0 BNE BACK Статья 23 Смещения При написании на языке ассемблера программ с использо- ванием циклов, мы сталкиваемся с определенными трудностя- ми. Дело в том, что в большинстве случаев в массиве Т не ис- пользуется (и не включается в его состав) элемент Т(0). В та- ком массиве имя Т на языке ассемблера соответствует ячейке, содержащей Т (1).
Смещения 83* На рис. 23.1 приведены адреса десяти переменных от Т(1) до Т(10), начиная с адреса 0859. Заметьте, что эти адреса сов- ладают с адресами на рис. 13.1. Однако адрес Т на рис. 13.1 был равен 0858; здесь адрес Т равен 0859. В статье 13, желая обратиться к элементу Т(6), мы исполь- зовали в команде адресное выражение Т+16. В результате по- лучался адрес 0858-]-6=085Е. Для массива из рис. 23.1 мы? должны были бы воспользоваться выражением Т+16—11 (или’ Переменная Адрес Т(1) 0859 (=0859-1 + 1) Т(2) 085А (=0859-1 + 2) Т(3) 085В ( = 0859-1+3) Т(4) 085С (=0859-1+4) Т(5) 085D (=0859-1+5) Т(6) 085Е (=0859-1+6) Т(7) 085F (=0859-1+7) Т(8) 0860 (=0859-1 + 8) Т(9) 0861 (=0859-1+9) Т(10) 0862 (=0859-1 + 10) Рис. 23.1. Адреса элементов массива, начинающегося с Т(1). Т+15). В результате получится адрес 0859+6—1 (или 0859+ +5), т. е. снова 085Е. В той же статье 13 для обращения к Т (J) мы использовали' команду LDA, Т, Y, если индекс содержался в регистре Y. Здесь придется использовать LDA Т—11, Y. Адрес Т—11 равен 0859—1, или 0858, и тогда содержимое регистра Y (т. е. J) прибавляет- ся к 0858, как это было и в статье 13. Величина —11 в обоих примерах представляет собой смеще- ние. Если только массив начинается с элемента Т(к), где к=+0 (в нашем примере /г=1), то k является смещением и должно вычитаться из Т в ситуациях, подобных описанным. Адрес мас- сива Т (т. е. адрес Т(£)), уменьшенный на величину k, дает ад- рес, по которому размещался бы элемент Т(0), если бы он при- сутствовал в массиве. Использование смещений позволяет заметно упростить про- грамму в тех случаях, когда индекс является суммой или раз- ностью константы и переменной (или еще более сложным выра- жением). Пусть, например, требуется загрузить в регистр А элемент T(J+6). Это можно сделать так: 6*
«4 Статья 23 LDA J ; Загрузить J и не забыть CLC ; сбросить флажок переноса ADC #!6 ; перед прибавлением 6 ТАХ ; Загрузить J+6 в регистр X, LDA т, X ; чтобы извлечь T(J+6), «о проще написать LDX J ; Установить индекс J LDA Т + 16, X ; Извлечь T(J+6) Выражение Т+16 обозначает адрес массива Т плюс 6; к этому адресу прибавляется значение J, находящееся в регистре X. Это в точности то же, что прибавить J+6 к адресу Т, как и было -сделано в предыдущем примере. Аналогично, если требуется за- писать содержимое регистра А в ячейку T(J—1) массива, начи- нающегося с Т(0), это можно сделать так: LDX J ; Загрузить J в регистр X DEX ; Вычесть 1 и получить J—1 STA Т, X ; Записать содержимое А вТ(Л—1) но проще написать LDX J ; Загрузить J в регистр X STA Т—11, X ; Записать содержимое А в Т (J—1) Здесь вместо того чтобы прибавлять J —1 к адресу Т, мы при- бавляем J к величине, равной адресу Т минус 1, что дает тот же результат, но позволяет уменьшить программу на одну ко- манду. В качестве примера использования смещения в циклах, пе- репишем программу из статьи 21 (очистка элементов массива от Т(1) до T(N)). Пусть массив Т начинается с Т(1), как пока- зано на рис. 13.1: LDX ТХА #!0 ; Установить J = 0 ; Очистить регистр A LOOP INX ; Установить J —J+l STA T— 11, X ; Установить T(J) =0 СРХ N ; Сравнить J c N BNE LOOP ; Если не равны, повторить Обратите внимание на то, что программа в статье 21 не очи- дцала Т(0), даже если предполагалось, что массив Т содержал этот элемент. Если программа должна очищать и Т(0) (очи-
Смещения 85 щая, таким образом, в общей сложности N-f-1 байт), мы можем просто изменить начальное присваивание: LDX =MFF ; Установить J = —1 ($FF= —1) LDA #!0 ; Очистить регистр A LOOP INX ; Установить J=J-f-l STA T, X ; Установить T (J) =0 CPX N ; Сравнить J c N BNE LOOP ; Если не равны, вернуться Начинать цикл установкой Х= —1, а затем немедленно выпол- нить INX, чтобы занести в регистр 0 — это весьма распространен- ный прием, которым вы будете часто пользоваться. В ассембле- ре LISA 2.5 (но не LISA 1.5) в команде LDX можно заменить на ф! —1 (или на 1). (Заметьте, что шестнадца- теричные числа обычно предполагаются числами без знака). В качестве другого примера мы можем заменить STA Т, X во второй программе статьи 22 на STA Т—!1, X, и в результате программа очистит все N байт массива Т, в том числе и Т(0). В ряде случаев это может оказаться полезным, даже если мы не рассматриваем наш массив Т как аналог массива языка Бейсик. Упражнения 1. Выполните упр. 1 предыдущей статьи в предположении, что массив Т начинается с Т(1) (т. е. Т(0) отсутствует). 2. Напишите предложения Бейсика, соответствующие приве- денным ниже последовательностям предложений на языке ас- семблера. Считайте, что массивы начинаются с Т(0) а) LDX К LDA T+!3, X STA T+14 * б) LDA I SEC SBC J TAY LDA T—12, Y STA S в) LDX M LDA T + !3, X TAX
86 Статья 24 LDA Т+!2, X STA Т, X 3. Покажите, как можно оптимизировать приведенные ниже последовательности предложений на языке ассемблера (см. упр. 3, статья 18). * а) LDX D INX • LDA Т, X б) LDY Е DEY LDA Т, Y * в) LDA N CLC ADC #!10 ТАХ LDA Т, X Статья 24 Символьные коды Как мы уже знаем, последовательность из 8 единиц и нулей, находящаяся в ячейке памяти, может представлять: а) число без знака; б) число со знаком; в) часть 16-разрядного числа, занимающего 2 байта; г) часть кода команды (содержащего 1, 2 или 3 байта). Кроме того, указанная последовательность может служить кодом символа. Существует много виДов симво- лов, в том числе: 1) буквы (А, В, С и т. д. до Z); 2) цифры (0, 1, 2 и т. д. до 9); 3) пробел (например, «А Z» — это А, пробел Z); 4) специальные символы (., 4-* = ;: $ и т. д.). В таблице П9 и П10 приведены символьные коды символов, используемые в ЭВМ Эпл: в табл. П10 — коды специальных символов, в табл. П9 — всех остальных.
Символьные коды 87 Каждый символ имеет несколько кодов, соответствующих различным режимам. 4 режима определяют характер высвечи- вания символа на экране дисплея: 1) нормальный режим (светлые символы на темном фоне); 2) инверсный режим (темные символы на светлом фоне); 3) режим мерцания (периодическое переключение с нор- мального на инверсный режим и наоборот); 4) нижний регистр (а, Ь, с и т. д. вместо А, В, С; эта воз- можность реализована в Эпл Пе, а в Эпл 11+ требует допол- нительных аппаратных средств) Два режима — нормальный и управляющий — определяют тип символов, вводимых с клавиатуры терминала Эпл. Чтобы ввести управляющий символ, надо нажать клавишу CTRL (cont- rol— управление), и пока эта клавиша не отпущена, нажать клавишу символа. В результате, если, держа нажатой клавишу CTRL, мы нажимаем клавишу X, получается комбинация «ctrl-Х». Ctrl — Н эквивалентно символу возврата на шаг (-<-), кото- рый позволяет нам возвращаться к введенным ранее символам и исправлять ошибки. Ctrl — М эквивалентно символу возвра- та каретки, который вводится в конце каждой строки; после воз- врата каретки следующая строка начинается с позиции 1. Ко- нечно, у экрана терминала нет никакой каретки (как у многих пишущих машинок), но термин по историческим причинам сох- ранился. И возврат на шаг, и возврат каретки имеют закреп- ленные за ними клавиши, но большинство управляющих симво- лов специальных клавиш не имеет. Перечень управляющих сим- волов с закрепленными значениями приведен в табл. П9. Любая команда, которая может выполнять действие над ко- нстантой, может также использовать в качестве операнда сим- вольную константу, заключенную в двойные кавычки. Так, ко- манда СМР #"+" сравнивает содержимое регистра А с шест- надцатеричным числом АВ, которое является кодом символа плюс (в нормальном режиме, который обычно и используется при вводе и выводе символов). Конечно, приведенная команда эквивалентна следующим: СМР #$АВ или СМР #%10101011 или СМР #1171 Символьные константы могут включаться в адресные выраже- ния. Пусть, например, регистр А содержит какую-либо шестнадцате- ричную цифру от 10 до 15. Вычтя 10 и добавив "А", мы полу- В Эпл Пе также имеется режим инверсного нижнего регистра клавиа- туры, который может устанавливаться по желанию пользователя вместо режима мерцания.
88 Статья 24 чим символьный код соответствующего шестнадцатеричного символа (от А до F). Если флажок переноса сброшен, то эту операцию можно выполнить одной командой ADC #"А"—!10. Адрес Шестнадцатерич- ное Символьный код 0А40 С5 Е 0А41 СЕ N 0А42 D4 Т 0А43 С5 Е 0А44 D2 R 0А45 АО пробел 0А46 D4 Т 0А47 С8 Н 0А48 С5 Е 0А49 АО пробел 0А4А С6 F 0А4В С9 I 0А4С D2 R 0A4D D3 S 0А4Е D4 т 0A4F АО пробел 0А50 СЕ N 0А51 D5 и 0А52 CD М 0А53 С2 В 0А54 С5 Е 0А55 D2 R Рис. 24.1. Адреса символов в символьной строке в памяти. Для любого символа с можно использовать "с как сокращен- ную форму #"с", однако форма "с не может входить в адрес- ное выражение. Английское слово, фраза, предложение или абзац рассматри- ваются ЭВМ как последовательность символов, часто называе- мая строкой символов. Каждый символ представлен своим ко- дом. Каждый код занимает ячейку, и все эти ячейки образуют
Символьные коды 89 массив. Например, фраза ENTER THE FIRST NUMBER1) мо- жет быть записана в ячейках с адресами от 0А40 до 0А55, как показано на рис. 24.1 (обратите внимание на символы пробе- лов). Необходимо понимать, что символьный код цифры совсем не то же самое, что сама цифра; так, код цифры 5 — это не 5, а 181 (десятичное), или В5 (шестнадцатеричное). Однако коды цифр расположены упорядоченно; код цифры п всегда на п больше, чем код нуля. Заметьте также, что символьная констан- та в кавычках должна представлять собой один символ; нель- зя, например, написать ^„CR", чтобы обозначить код возврата каретки, хотя допустимо использовать для этого выражение * $8D (см. табл. П9). Символьный код, используемый в Эпл, представляет собой форму кода ASCII (American standard code for information in- terchange— Американский стандартный код обмена информа- ции). Различные формы ASCII используются на всех микро- ЭВМ.. Ассемблер LISA воспринимает также вторую форму ASCII, в которой все коды букв и цифр содержат в самом ле- вом разряде не 1, а 0. Так, обычный символьный код цифры 5 — шестнадцатеричное В5, или двоичное 10110101. Этот код может быть представлен, как "5" (кавычки двойные). Во второй фор- ме символьный код цифры представляет двоичное 00110101 (где в самом левом разряде содержится не 1, а 0, а остальная часть кода та же), или шестнадцатеричное 35. Этот код может быть представлен, как '5' (кавычки одинарные). Символьный код можно использовать для передачи секрет- ных сообщений (вспомним статью 1). Так, фраза PIANO LESSONS STINK в закодированной форме выглядит так: DO С9 С1 СЕ CF АО СС С5 D3 D3 CF СЕ D3 АО D3 D4 С9 СЕ СВ Упражнения 1. Расшифруйте следующие сообщения, записанные в сим- вольном коде: a) D3 С5 С5 АО CD С5 АО Cl D4 АО D3 С9 D8 б) D6 С5 СС CD Cl АО С4 D9 С5 D3 АО С8 С5 D2 АО С8 Cl С9 D2 в) D9 CF D5 D2 АО С6 СС D9 АО С9 D3 АО CF D0 С5 СЕ. 2. Закодируйте приведенные ниже сообщения с помощью символьного кода: *> Введите первое число. — Прим, перев.
90 Статья 25 * a) NUMBER ТОО BIG б) LABEL ТОО LONG * в) ILLEGAL CODE 3. Напишите программу на языке ассемблера, которая зано- сит — 1 в регистр X, если Q содержит символьный код дефиса (знака минус), и очищает регистр X в противном случае. Ис- пользуйте двойные кавычки. Статья 25 Ввод-вывод, подпрограммы и «EQU» Ввод-вывод в ЭВМ Эпл осуществляется с помощью подпро- грамм. Для вызова подпрограмм используется команда JSR (Jump to subroutine — переход на подпрограмму). Так, коман- да JSR S вызывает подпрограмму S. (В статье 60 мы рассмот- рим, как выполняется команда JSR, а в статьях 69 и 70 — как пишутся сами подпрограммы ввода-вывода.) Мы будем часто использовать такие системные подпрограм- мы Эпл, как COUT (Character out — вывод символа), которая выводит на экран символ из регистра A; RD KEY (Read key — ввод символа с клавиатуры), которая ожидает, пока вы введете с клавиатуры символ, а затем считывает его, заносит в регистр А и выводит на экран; GETLNZ, представляющая собой вари- ант GETLN (Get line — ввод строки), которая, используя под- программу RDKEY, считывает целую строку символов (до воз- врата каретки) и заполняет ими массив, начинающийся с адре- са $0200. Этот массив называется стандартным буфером вво- да, или INBUF О. Обычно для вывода символа на экран мы загружаем его в регистр А и вызываем COUT. Используя цикл, мы можем вы- светить N символов из массива Т: *> Буфером называется массив, используемый для организации ввода- вывода.
Ввод-вывод, подпрограммы и «EQU» 91 WLOOP LDY #!0 ; Установить индекс первого символа LDA Т, Y ; ; Загрузить этот символ JSR COUT ; Вывести его INY ; Сместиться к следующему символу CPY N : ; N символов выведено? BNE WLOOP; , Если нет, повторить Если мы хотим использовать COUT, мы должны указать ас- семблеру LISA, где эта подпрограмма находится. Оказывается, COUT имеет шестнадцатеричный адрес FDED. Поэтому где-то в программе мы должны написать COUT EQU $FDED Псевдокоманда EQU (от equals — равно) используется для при- своения символическому обозначению определенного значения. Приведенной строкой обозначению COUT присваивается зна- чение $FDED, так что JSR COUT — это то же самое, что JSR $FDED (в этой программе). Точно так же мы можем использовать RDKEY, вызвав эту подпрограмму, а затем записав содержимое регистра А в па- мять, либо используя его каким-либо иным образом. Так, про- грамма _ . JSR RDKEY ; Ввести 1 символ CMP #"Y" ; Символ "Y" (от "YES") ? BNE ANSRNO ; Если нет, переход на ANSRNO будет ожидать ввода символа с клавиатуры, а затем перейдет на ANSRNO, если этот символ не Y. Заметьте, что после JSR RDKEY нам нет необходимости использовать JSR COUT, чтобы высветить этот символ, так как подпрограмма RDKEY выполня- ет это сама. Если мы вызываем GETLNZ, то символы, которые мы вво- дим с клавиатуры, попадают в ячейки $0200, $0201, $0202 и т. д. Приведенная ниже программа считывает вводимую стро- ку и выводит ее на экран JSR GETLNZ ; Ввести одну строку символов LDX #$FF ; Начнем с индекса, =—1 JMP ЕСНО2 ; Получить первый символ ЕСНО1 JSR COUT ; Вывести этот символ ЕСНО2 INX ; Сместиться к следующему символу LDA INBUF, X ; Загрузить текущий символ СМР #$8D ; Это возврат каретки?
92 Статья 25 BNE STX ECHOI LENGTH ; Если нет, продолжить просмотр ; Записать число символов INBUF EQU $0200 ; Стандартный входной буфер GETLNZ EQU $FD67 ; Адрес программы GETLNZ COUT EQU $FDED ; Адрес программы COUT Заметьте, что если мы используем подпрограмму GETLNZ, мы должны сообщить LISA, где она находится, а также где распо- ложен массив INBUF; это делается предложениями EQU, как и для COUT. Оказывается, GETLNZ расположена по шестнад- цатеричному адресу FD67, a RDKEY — по адресу FD0C. Все эти адреса приведены в табл. П11, где COUT, GETLNZ, RDKEY и многие другие подпрограммы Эпл описаны более подробно. Псевдокоманду EQU можно использовать, чтобы давать име- на различным константам. В предыдущей программе мы могли написать CRET EQU $8D MINUS 1 EQU $FF a затем использовать CMP 41= CRET вместо СМРф$8О (или LDX MINUS 1 вместо LDX#$FF). Обычно мы считываем не посимвольно, а построчно, так как в этом случае можно исправлять ошибки. Если вы используете RDKEY, ЭВМ немедленно реагирует на нажатие любой клави- ши независимо от того, правильно она нажата или ошибочно. Следует также помнить, что RDKEY и GETLNZ используют для своей работы регистры X и Y. Поэтому, например, приведенная ниже программа ошибочна: LDY N ; Попытка обработать N символов RLOOP JSR RDKEY ; Ввести один символ (строки обработки этого символа) DEY ; Декремент регистра Y и возврат BNE RLOOP ; Эта команда не будет работать, как мы хотим В программе предполагается, что регистр Y будет отсчитывать N, N—1, N—2 и т. д. Однако этого не произойдет, так как RDKEY сама использует регистр Y, и команда DEY уменьшает на 1 не N, а какую-то другую величину. Если вы используете RDKEY в цикле, храните счетчик цикла в памяти, а не в ре- гистре Y. При использовании COUT этой проблемы не возника- ет, так как COUT сохраняет и восстанавливает регистры X и Y, Этот прием будет описан в статье 57.
Ввод-вывод, подпрограммы и <EQU» 93 Упражнения 1. Опишите своими словами, для чего служат приведенные ниже программы. (Не ограничивайтесь комментариями; вы должны описать, для чего служит вся программа, а не каждая строка в отдельности). * а) LDA #!10 STA LCOUNT LOOP JSR RDKEY LDX LCOUNT STA REV—!1, X DEC LCOUNT BNE LOOP б) JSR RDKEY CMP BNE P7 LDA P7 JSR COUT * в) JSR RDKEY CLC ADC CMP #"Z"+!1 BNE J4 LDA #"A" J4 JSR COUT 2. а) Системная подпрограмма CROUT (с адресом FD8E} выполняет возврат каретки *>. Напишите последовательность ко- манд, использующую COUT, эквивалентную предложению JSR CROUT. б) Системная подпрограмма GETLNZ выполняет возврат каретки, а затем вызывает системную подпрограмму GETLN по адресу FD6A. Мы можем использовать GETLN для ввода сим- волов, высвечивание которых необязательно производить с на- чала новой строки. (Подпрограммы GETLNZ и GETLN вводят символы совершенно одинаково: в обоих случаях коды поступа- ют в стандартный входной буфер, начинающийся с шестнадцате- ричного адреса 200. Разница между GETLN и GETLNZ заклю- *> На экране терминала это приводит к перемещению курсора в начало' следующей строки. После этого текст, выводимый на экран терминала, на- чинается с новой строки.— Прим, перев.
94 Статья 26 чается только в способе вывода этих символов на экран.) На- пишите последовательность команд с использованием GETLN, эквивалентную предложению JSR GETLNZ. jjc 3. Предположим, что в приведенной выше программе обра- щения к GETLNZ мы вместо LDA INBUF, X и CMP*$8D написали LDA* $8D и CMP INBUF, X. Как можно в этом случае оптимизировать программу? Замечание: обратите внимание на содержимое регистра А в цикле. Статья 26 Объявления констант В ассемблере LISA имеется псевдокоманда BYT, весьма схо- жая с DFS II. Однако она не только резервирует 1 байт (чем и объясняется ее имя BYT от Byte — байт), но и дает этому бай- ту определенное значение. Сравните следующие строки: Л DFS 11 ; Переменная JI J2 BYT 16 ; Переменная J2 co значением 6 J3 BYT $20 ; Переменная J3 co значением 32 J4 BYT "Q" ; Переменная J4 co значением «Q> К1 DFS 12 ; Двухбайтовая переменная KI К2 BYT $CD ; Двухбайтовая переменная K2 со BYT $AB ; значением $ ABCD (с переставл. байтами) L BYT $10—11 ; Переменная L со значением $F Присваиваемое значение содержит 8 бит. Если вы указываете значение, которое не укладывается в 8 разрядов, псевдокоманда BYT использует правые 8 бит из указанного вами значения: М BYT $ 1A3D ; Значение М равно $ 3D Ml BYT $256 ; Значение Ml равно $56 М2 BYT 1256 ; Значение М2 равно нулю
Объявления констант 9S Пусть теперь мы хотим определить строку символов, напри- мер слово ERROR, соответствующее метке ERR. В этом случае- следует написать: ERR BYT "Е" BYT "R" BYT "R" BYT "O" BYT "R" Однако имеется специальная псевдокоманда ASC (от «данные* в коде ASCII»), которая позволяет сделать то же самое проще: ERR ASC "ERROR" Ниже показано, как можно вывести на экран фразу ENTER1 THE FIRST NUMBER LDY PMSG1 LDA JSR INY CPY BNE #!0 ; Начать с первого символа MSG1, Y ; Загрузить текущий символ COUT ; Вывести этот символ на экран ; Сместиться к следующему символу 41=122 ; Все 22 символа выведены? PMSG1 ; Если нет, повторить Здесь MSG1 должна быть определена в секции данных следую- щим образом: MSG1 ASC "ENTER THE FIRST NUMBER" Возникает некоторая проблема, связанная с тем, что при под- счете символов (в данном случае их 22) легко ошибиться. Од- нако возникновение ошибки можно предупредить, если закон- чить сообщение кодом возврата каретки ($8D) MSG1 ASC "ENTER THE FIRST NUMBER" BYT $8D ; Возврат каретки и переделать программу так, чтобы она обнаруживала этот код: LDY PMSG1 LDA СМР BEQ JSR INY JMP ф!0 ; Начать с первого символа MMSG1, Y; Загрузить текущий символ #$8D ; Это возврат каретки? DONE ; Если да, закончить COUT ; Если нет, вывести символ на экран ; Сместиться к следующему символу PMSG1 ; и повторить DONE (следующая команда)
96 Статья 26 Можно сделать программу более наглядной, если вместо BYT $8D и CMP#$8D написать BYT CRET и СМР CRET (при этом в программу надо включить объявление CRET EQU $8D, см. предыдущую статью). Приведенную программу мож- но использовать для вывода на экран терминала содержимого стандартного буфера ввода (также см. предыдущую статью), поскольку текст, введенный с клавиатуры в этот буфер, всегда заканчивается кодом возврата каретки. Псевдокоманды BYT и ASC, так же как и DFS, носят назва- ние объявлений констант. В статьях 63 и 73 будут приведены дополнительные примеры объявлений констант. Вообще гово- ря, язык ассемблера требует объявления константы для каждой переменной. В то же время часто можно обойтись без псевдо- команды BYT. Например, вместо команды LDA J2, которой предшествует объявление J2 BYT 16, мы можем просто написать LDA #!6, что экономит два байта: один на объявлении J2 BYT 16, а другой из-за того, что команда LDA 41=16 занимает два бай- та, в то время как команда LDA J2 — три. Символ двойных кавычек сам может быть включен в строку символов, объявленную с помощью ASC, но каждый раз, когда он встречается в строке, его надо писать дважды. Так, объявле- ние константы ASC "ГМ NOT ""НОТ"" ГМ ""COOL"""1) приводит к.образованию строки ГМ NOT "НОТ", ГМ "COOL". Одиночные кавычки также можно использовать в' псевдоко- манде ASC, при этом образуется строка, в каждом байте кото- рой самый левый разряд будет установлен в 0 (см. статью 24). Символ одиночных кавычек может использоваться и внутри та- кой строки, но, как и в случае двойных кавычек, он должен пи- саться дважды. Так, предложение ASC Т’М NOT "НОГ, Г’М "COOL" образует строку ГМ NOT "НОТ", ГМ "COOL", при этом каж- дый байт этой строки будет содержать 0 в самом левом разряде. В строке может встречаться точка с запятой. (Отсюда следу- ет, что точка с запятой, стоящая внутри кавычек, не рассматри- вается как начало комментария.) Упражнения 1. Напишите предложения с псевдокомандой BYT, опреде- ляющие: *> Мне не «жарко», мне «холодно» — Прим, перев.
Программный счетчик и относительная адресация 97 а) переменную с именем К и значением 99; б) переменную с именем KPRIME и значением, соответству- ющим символьному коду знака плюс; в) переменную с именем U, имеющую двухбайтовое шест- надцатеричное значение $DAFF с переставленными байтами. >{< 2. Каково конечное значение переменной К, вычисляемое приведенной ниже программой. (Подсказка: используйте табл. П9.) ORG $0800 LDA KI CLC ADC К2 ADC КЗ STA к ORG $0900 К DFS 11 KI BYT "С" К2 BYT $22 КЗ BYT % 10101 END 3. Напишите другой вариант первой программы данной ста- тьи, которая выводит текст ENTER THE -FIRST NUMBER, ис- пользовав при этом DEY вместо INY. Пусть программная сек- ция начинается с адреса $0800, а секция данных — с адреса $08АО, и предположите, что ранее уже было предложение COUT EQU $FDED. (Подсказка: здесь придется по-другому использовать ASC. Почему и как именно?) Статья 27 Программный счетчик и относительная адресация Кроме регистров А, X и Y, в процессоре 6502 имеется целый ряд внутренних регистров. Термин «внутренний» означает, что мы не можем ни загружать эти регистры, ни вообще как-либо к ним обращаться, так как они используются исключительно са- 7—589
98 Статья 27 мим процессором. К этим регистрам относится 8-разрядный ре- гистр команд IR (Instruction Register), в котором содержится код выполняемой операции, а также 16-разрядный программ- ный счетчик PC (Program Counter), в котором содержится ад- рес выполняемой команды. ЭВМ всегда выполняет машинный цикл следующим образом: 1) Загружает IR кодом из ячейки, адрес которой содержится в PC, и добавляет 1 к содержимому PC (этот шаг называется выборкой команды). 2) Выполняет команду, код которой содержится в IR. Од- нако еще перед выполнением команды: а) если команда состоит из двух байтов, специальный внут- ренний регистр (не IR или PC) загружается кодом из ячейки, адрес которой содержится в PC, а содержимое PC увеличива- ется на 1, как и в шаге 2; б) если команда состоит из трех байтов, два внутренних ре- гистра загружаются кодами из ячеек с адресами п и п-Н, (здесь п — содержимое PC), а затем содержимое PC увеличива- ется на 2’>. 3 Возвращается к шагу 1. Важно отметить, что при выполнении текущей команды (шаг 2) PC всегда содержит адрес следующей команды в последова- тельности команд; но к началу выполнения шага 3 регистр PC должен содержать адрес той команды, которую надо выпол- нить, что не то же самое, если в этот момент происходит пере- ход. Это важно знать, чтобы правильно определить адреса в командах переходов. В процессоре 6502 предусмотрены два вида команд перехо- да на метку — переходы (jumps) JMP и JSR и ветвления (branches) ВСС, BEQ и другие1). Это не произвольный выбор слов; указанные виды команд отличаются по своему машинно- му представлению. Переход использует адрес так же, как загрузка, запись и т. д. Так, машинный код команды JSR COUT составляет 20 ED FD. Здесь 20 — код операции JSR, а за ним следует адрес COUT (FDED) с переставленными, как обычно, байтами. Ветвление на метку L, однако, использует специальную фор- му адреса, называемую относительным адресом. В то время как обычный адрес представляет собой 16-разрядную величину без знака, относительный адрес — это 8-разрядная величина со * 1 2 *> Если выполняется команда JMP, содержимое этих двух внутренних регистров передается в два байта PC, потому что после перехода на шаг 1 мы хотим выполнить именно ту команду, на которую произошел переход. 2> Чтобы различать команды jump и branch, сравнение которых проводит- ся в настоящей статье, будем пользоваться терминами «переход» и «ветвле- ние» (см. также примеч. на с. 63).— Прим, перев.
Программный счетчик и относительная адресация 99 знаком. Если происходит ветвление, относительный адрес при- бавляется к содержимому программного счетчика в шаге 2 (см. выше). Каждая команда ветвления занимает два байта, один — для кода операции и другой — для 8-разрядного относительного, ад- реса. Если команда ветвления расположена по адресу СА (те- кущий адрес)’), то PC на шаге 2 содержит СА+2. Если теперь RA — относительный адрес, то CA4-24-RA составляет адрес ветвления ВА, т. е. адрес L, куда осуществляется переход. Рассмотрим в качестве примера следующие команды: Машинный язык Язык ассемблера 0840 AD LOOP LDA N 0843 F0 05 BEQ CON 0845 20 JSR F 0848 90 F6 BCC LOOP 084А 8D CON STA Q 16-разрядные адреса N, F и Q в данном случае несущест- венны. Для нас важно разобраться в вычислении относитель- ных адресов 05 и F6. Рассмотрим сначала команду BEQ CON. Выше мы получили равенство CA+2+RA=BA. В данном слу- чае СА, текущий адрес, равен 0843; ВА, адрес ветвления — 084А. Отсюда 0843+2+RA=084A, RA равен 5 (или 05 при представлении двумя шестнадцатеричными цифрами). Рассмотрим теперь команду ВСС LOOP. Наше равенство по-прежнему имеет вид CA+2-j-RA=BA; на этот раз СА, теку- щий адрес, равен 0848, а ВА, адрес ветвления, — 0840. Отсюда 08484-2+RA=0840, и RA= —10 (десятичное), или F6 (шест- надцатеричное в форме дополнения до двух). При написании программ на языке ассемблера полезно пом- нить, что команды ветвления состоят только из 2 байт, в то вре- мя как команды переходов — из трех. Например, если в нашей программе имеются строки ВСС ALPHA ; Если флажок переноса сброшен, перейти на ALPHA JMP ВЕТА ; Перейти на ВЕТА то можно сэкономить 1 байт, сделав следующую замену: ВСС ALPHA ; Если флажок переноса сброшен, перейти на ALPHA BCS BETA ; Если установлен, перейти на ВЕТА О СА Current Address (текущий адрес); RA (см. далее)—Relative Address (относительный адрес); ВА — Branch Address (адрес ветвления).— Прим, перев. 7*
100 Статья 27 Заметьте, что в данной программе BCS будет всегда осущест- влять переход. Более важным является то обстоятельство, что нельзя осу- ществить ветвление более чем на 129 байтов вперед или 126 байтов назад, потому что относительный адрес — это 8-разряд- ная величина со знаком *>. Если требуется осуществить ветвле- ние на более далекую метку, то мы должны вместо, например, BEQ ALPHA ; Если равно, перейти на ALPHA написать* 2); BNE ВЕТА ; Если не равно, не переходить на ALPHA JMP ALPHA ; В противном случае перейти на ALPHA ВЕТА (следующая команда) Упражнения 1. Преобразуйте следующую программу в машинный язык, обратив особое внимание на правильное вычисление всех от- носительных адресов: INBUF EQU $0200 FZERO EQU $0860 ERRORZ EQU $0888 ORG $0840 LDX #!255 CHECKZ LDA INBUF, X BNE DEX FZERO BNE CHECKZ STA END ERRORZ 2. Определите содержимое программного счетчика в про- грамме п. 1, когда выполняется команда STA. (Подсказка: этот адрес не равен 084А. Почему?) 3. Напишите программу на языке ассемблера, соответству- ющую приведенной ниже машинной программе. Предположите, Заметьте, что это уменьшает возможность замены JMP условным вет- влением (см. пример выше). 2> Некоторые программисты, имеют обыкновение всегда ставить JMP после условного ветвления, что неправильно. Не забывайте, что вы можете заменять BEQ — JMP на BNE, BNE — JMP на BEQ и т. д , если только пе- реход не происходит на «слишком далекую» метку.
Флажок знака и команды сравнения 101 что команда BNE осуществляет переход на метку FZERO, и оп- ределите FZERO с помощью EQU, как это было сделано в п. 1. 08Е0 А2 FF 08Е2 Е8 08ЕЗ DD 00 02 08Е6 F0 FA 08Е8 D0 06 Статья 28 Флажок знака и команды сравнения Каждая команда процессора 6502, которая устанавливает флажок нуля, также устанавливает в соответствии со знаком результата ’> другой флажок, а именно флажок (признак) знака (1, если результат отрицательный, и 0 в противном случае). Две команды, BPL (Branch on plus — переход по плюсу) и BMI (Branch on minus — переход по минусу), используют флажок знака аналогично тому, как команды BEQ и BNE используют флажок нуля: BPL L Переход на L, если флажок знака сброшен BM.I L Переход на L, если флажок знака установлен Так, организовать переход на метку QNEG, если Q отрицатель- но, можно следующим образом: LDA Q LDX Q LDY Q BMI QNEG ИЛИ BMI QNEG ИЛИ BMI QNEG Или мы можем сложить два числа со знаком Р и Q и сразу пе- рейти на SUMPOS, если сумма неотрицательна (т. е. положи- тельна или нуль): о Исключение из этого правила дано в статье 55.
102 Статья 28 LDA Р ; Загрузить Р и прибавить Q CLC ; (сбросив предварительно флажок переноса) ADC Q ; Прибавление Q устанавливает флажок знака BPL SUMPOS ; Если неотрицательно, перейти на SUMPOS Использование флажков нуля и знака различно. В статье 20 описывалась процедура проверки условия P=Q. Если P=Q, то Р — Q=0, поэтому проверить условие P=Q можно с помо- щью команды сравнения (производящей вычитание), после ко- торой анализируется флажок нуля. Очевидно, что если P<Q, то Р —Q<0. Однако мы не можем проверить условие P<Q (или любое другое условие неравенства) с помощью команды срав- нения, за которой стоит команда анализа флажка знака. При этом не играет роли, являются ли Р и Q величинами со знаком или без знака. Разберемся, почему же так получается. Рассмотрим сначала числа без знака. Если Р=3, a Q=5, то Р —Q= —2, т. е. ре- зультат, несомненно, отрицателен. Но предположим, что Р= = 100, a Q=229. Тогда Р —Q= —129, т. е. результат должен быть отрицателен; однако, будучи записан в 8 разрядов, он та- ковым не является. Действительно, 129 (десятичное 128+1) представляется двоичным 10000001 и имеет, таким образом, отрицательный знак; анализ этого числа с помощью соответст- вующих команд дает ответ, что оно отрицательно. Дополнение до двух этого числа равно 01111111; оно должно представлять — 129, однако имеет положительный знак. Та же проблема возникает, если Р и Q — числа со знаком. Пусть Р=— 29, a Q=100. Тогда Р—Q опять равно —129, и снова результат в 8-разрядном представлении не имеет отри- цательного знака. Решение задачи для чисел без знака заключается в исполь- зовании флажка переноса. Вспомним, что если P<Q и мы вы- читаем Q из Р (или сравниваем Р и Q), то возникает заем и флажок переноса сбрасывается. Таким образом, если исполь- зовать команды ВСС L, переход на L будет происходить при P<Q: LDA Р СМР Q или ВСС L LDX Р СРХ Q ИЛИ LDY Р CPY Q ВСС L ВСС L Аналогично можно организовать мощью команды BCS: переход на L при P^Q с ПО' LDA Р LDX р LDY Р СМР Q или СРХ Q или CPY Q BCS L BCS L BCS L
Флажок знака и команды сравнения 103 Проверку условия P^Q можно заменить проверкой эквива- лентного условия Q^,P: LDA Q LDX Q LDY Q CMP P или СРХ P или CPY P BCS L BCS L BCS L Точно так же вместо проверки условия P>Q можно проверять условие Q<P: LDA Q LDX Q LDY Q CMP P или СРХ P или CPY P BCC L BCC L BCC L Написанные выше последовательности команд весьма полезны. Если вы не можете их запомнить, обращайтесь к ним по мере необходимости как к таблице. Сравнение чисел со знаком бу- дет рассмотрено в статьях 54 и 56. При использовании конструкций, подобных описанным, не- сколько раз в одной программе, следите за тем, чтобы у вас не повторялись одинаковые метки (например, L, как в приведен- ных примерах). Все метки в программе на языке ассемблера должны различаться, так же как в программе на языке Бейсик не должно быть нескольких строк с одним номером *>. К настоящему времени мы познакомились с 4 регистрами, именно А, X, Y и программным счетчиком, а также с тремя флажками: нуля, переноса и знака. В процессоре 6502 исполь- зуется гораздо больше регистров и флажков; все они описаны в табл. П12. Следует изучить также второй столбец табл. ПЗ, где указано, какие флажки устанавливаются каждой командой. В этой таблице Z соответствует флажку нуля (Zero — нуль), С — переноса (Carry — перенос), S — знака (Sign — знак) и V — еще одному флажку, с которым мы познакомимся в статье 56. Остальные регистры и флажки будут подробно описаны в последующих главах. Ассемблер LISA представляет возможность писать BLT (Branch on Less Than — переход, если меньше) вместо ВСС, и BGE (Branch on Greater or Equal — переход, если больше или равно) вместо BCS. Использование этих команд делает про- грамму более наглядной, так как не надо каждый раз вспоми- нать, почему вы использовали переход по условию установки или сброса флажка переноса. *> Исключением из этого правила является локальные метки в языке ас- семблера LJSA 2.5.
404 Статья 28 Упражнения 1. Напишите предложения языка Бейсик, соответствующие приведенным ниже последовательностям команд на языке ас- семблера. Пусть метке FIFTY соответствует номер строки 50, массив Т начинается сТ(0), а массив U — cU(l): а) LDA Q1 CLC ADC Q2 СМР #110 вес FIFTY - * б) LDY J LDA U—11, Y LDY К СМР U—11, Y BCS FIFTY ’г в) LDA W LDX К СМР Т + 15, X вес FIFTY 2. Напишите программы на языке ассемблера, соответству- ющие приведенным ниже предложениям языка Бейсик. Псев- докоманды ORG, END и DFS можете опустить. Пусть номеру строки 50 соответствует метка FIFTY, массив Т начинается с Т(0), а массив U —с U(l): * a) IF A2> = T(K+D THEN 50 б) IF U(5)<=U(6) THEN 50 * в) IF U(U(J))>2 THEN 50 3. Почему ошибочен следующий способ перехода на ONE, если величина со знаком J положительна, на TWO, если J рав- на нулю, и на THREE, если J отрицательна? LDA J BPL ONE BEQ TWO JMP THREE I
Сравнение двухбайтовых величин 105 Статья 29 Сравнение двухбайтовых величин Когда мы складываем два 2-байтовых числа, мы делаем это справа налево. Однако, когда мы сравниваем те же числа, мы делаем это слева направо. Предположим, что мы сравниваем 19 и 25; мы не можем начать справа, так как хотя 9 больше 5, но 19 меньше 25. Мы говорим, что 9 и 5 менее значимы, чем 1 и 2. В 2-байтовой переменной ав а — старший байт и в — млад- ший байт. Аналогично в переменных, состоящих из двух шест- надцатеричных (или десятичных) цифр, мы различаем старшую и младшую цифры. Приведенная ниже программа осуществляет переход на мет- ку ALPHA, если P<Q в предположении, что Р и Q—16-раз- рядные переменные с переставленными байтами. LDA Р+11 ; Сначала сравнить старшие байты СМР Q+n ; Если не равны и флажок переноса сброшен, BNE DECIDE ; то Р меньше Q LDA Р ; В противном случае сравнить младшие байты СМР Q ; Снова Р меньше Q, DECIDE ВСС ALPHA ; если флажок переноса сброшен В предыдущей статье было показано, что если после сравнения Р и Q флажок переноса сброшен, то P<Q. Предположим те- перь, что старшие байты (Р-|-!1 и Q4-I1) не равны. В этом слу- чае младшие байты не играют роли, мы переходим на DECIDE и сравниваем только старшие байты (команда BNE не изменя- ет состояние разряда переноса). Если же старшие байты равны, мы должны сравнить остав- шиеся два байта (при сравнении двухбайтовых чисел ab и ас условие ab<.ac эквивалентно &<с). Заметьте, что для сравне- ния обеих пар байтов используется одна и та же команда ВСС. Если требуется перейти на ALPHA по условию P^,Q, мы можем просто заменить ВСС на BCS: LDA Р+11 ; Сначала сравнить старшие байты СМР Q+11 Если не равны и флажок переноса установлен, BNE DECIDE ; то Р не меньше Q
106 Статья 29 LDA Р CMP Q DECIDE BCS ALPHA ; В противном случае сравнить младшие байты ; Снова Р не меньше Q, ; если флажок переноса установлен Если требуется перейти на ALPHA по условию P^Q, мы, как и раньше, проверяем условие Q^P: LDA Q+I1 CMP Р+11 BNE DECIDE LDA Q CMP P DECIDE BCS ALPHA ; Сначала сравнить старшие байты ; Если не равны и флажок переноса установлен, ; то Q не меньше Р ; В противном случае сравнить младшие байты ; Снова Q не меньше Р, ; если флажок переноса установлен Наконец, для перехода на ALPHA по условию P>Q, мы проверяем условие Q<P: LDA Q+11 Сначала сравнить старшие байты СМР Р+11 Если не равны и флажок переноса сброшен, BNE DECIDE то Q меньше Р LDA Q В противном случае сравнить младшие байты СМР Р Снова Q меньше Р, DECIDE ВСС ALPHA если флажок переноса сброшен Как и при 8-разрядном сравнении, 16-разрядное сравнение не требует обязательного использования регистра А. При необ- ходимости мы можем всюду в программах заменить LDA на LDX или LDY, а СМР — на СРХ или CPY. Как уже отмечалось в предыдущей статье, если конструкции, подобные описанным выше, повторяются в пределах одной программы, в них необ- ходимо на месте DECIDE использовать различные метки. Старший байт 2-байтового числа можно также называть, как это было сделано в статье 12, левой половиной или старшим полусловом, а младший байт — правой половиной или млад- шим полусловом. Упражнения 1. Напишите программу на языке ассемблера для перехода на метку LESS, если P+Q<R, где Р, Q и R—16-разрядные величины с переставленными байтами. Опустите ORG, END и DFS. (Подсказка: вам понадобится место для временного хра- нения младшего полуслова P+Q, пока оно не будет использо- вано в операции сравнения. Сохраняйте его в регистре X). 2. Напишите программу на языке ассемблера для перехода на метку LESS, если P<Q, где Р и Q — 24-разрядные величи- ны с переставленными байтами (не пользуйтесь циклом).
Инкремент, декремент и дополнение двухбайтовых величин 107 3. Напишите программу на языке ассемблера для перехода на метку LESS, если P<Q, где Р и Q — N-байтовые величины (N — целочисленная переменная со знаком). Используйте цикл. (Начните с LDX N и LDA Р—! 1, X. Обратите особое внимание на возможность равенства Р и Q.) Статья 30 Инкремент, декремент и дополнение двухбайтовых величин При выполнении целого ряда операций с двухбайтовыми ве- личинами используются уже известные нам команды. Рассмот- рим, например, инкремент (прибавление 1) двухбайтовой вели- чины. Следует отдавать себе отчет в том, что просто прибавле- ние 1 к младшему полуслову (или к обоим полусловам) числа не дает требуемого результата. Рассмотрим несколько случаев прибавления 1 к 2-разрядному числу. 26 83 49 + 1 + 1 + 1 27 84 50 Можно подметить, что обычно 1 прибавляется к младшей циф- ре, в то время как старшая остается без изменения. Исключение составляет случай, когда младшая цифра становится нулем, то- гда 1 прибавляется к старшей цифре. Воспользовавшись этим алгоритмом, можно написать на языке ассемблера программу прибавления 1 к 2-байтовой величине V с переставленными байтами: INC V ; Инкремент младшего полуслова BNE Р17 ; Установил ли инкремент флажок нуля? INC V-4-И ; Если да, прибавить 1 к старшему ; полуслову Р17 (следующая команда)
108 Статья 30 Рассмотрим теперь вычитание 1 из 2-разрядной величины. Из примеров вычитания 1 из 2-разрядного числа 27 84 50 - 1 — 1 - 1 26 83 49 видно, что 1 вычитается из младшей цифры, а если младшая цифра была нулем, то и из старшей цифры. Поэтому мы не можем использовать BNE вслед за DEC; в этом случае мы про- веряли бы возникновение нуля в результате вычитания. Про- грамма вычитания 1 из V на языке ассемблера должна выгля- деть так: LDA V ; ; Посмотрим на младшее полуслово BNE Р17 ; ; Оно нулевое? DEC V+!l ; ; Если да, декремент старшего полуслова ' Р17 DEC V ; ; В любом случае декремент младшего ; полуслова В этом примере вместо LDA можно было использовать LDX или LDY. Простейший способ получить дополнение до двух 2-байтовой величины заключается в вычитании значения этой величины из нуля по правилам вычитания 2-байтовых чисел, изложенных в статье 17. Так, вычисление V2= —VI, где VI и V2 — 2-байто- вые величины (как и использованная выше переменная V), мо- жет быть произведено следующим образом: LDA #10 ; Вычесть младшее полуслово VI SEC ; (предварительно установив флажок пе- ; реноса) SBC VI ; из младшего полуслова 16-разрядной STA V2 ; нулевой константы (оба байта 0) LDA #!0 ; Теперь вычесть старшее полуслово SBC vi+n ; из нуля с учетом займа STA V2+!l ; из-за предыдущего вычитания Заметьте, что дополнение до двух 2-байтовой величины не образуется как дополнение до двух каждого байта в отдельно- сти. В самом деле, если cd — дополнение до двух числа ab, то d — действительно дополнение до двух цифры Ь, однако с — до- полнение до единицы цифры а, если только не выполняется ра- венство d=b=O. Можно отметить любопытный факт: в обоих приведенных примерах команда BNE всегда передает управление на 5 байт
Инкремент, декремент и дополнение двухбайтовых величин 109 вперед. Из этих 5 байт два занимает сама BNE и три — следую- щие за ней INC или DEC. Команду BNE, передающую управ- ление на 5 байт вперед, можно записать следующим образом: BNE *+!5 Символ >|< (иногда используется $ или .) можно обнаружить в любом языке ассемблера. Он обозначает содержимое счетчика текущего адреса; так, например, записи JMP *+!17 и ALPHA JMP- ALPHA + I17 . эквивалентны. В принципе счетчик текущего адреса можно ис- пользовать во всех командах перехода в программе; это, одна= ко, рискованно, и вот почему. Если вы модифицируете свою программу, вставляя в нее новые команды или убирая старые, вы можете изменить относительное расположение строк про- граммы. Пусть, например, вы написали адрес перехода в виде ALPHA+I17, имея в виду метку ВЕТА, а потом изменили текст программы между метками ALPHA и ВЕТА. Это почти неми- нуемо приведет к изменению числа байтов, расположенных между ALPHA и ВЕТА с 17 на какое-то другое число п и ALPHA+H7 надо будет изменить на ALPHA-j-!n. Легко забыть о необходимости такого изменения, и в результате в программе появится ошибка, которую почти невозможно обнаружить. В та- кой программе управление будет передаваться на байт, который, возможно, не является кодом операции;- он может содержать данные, или быть одним из двух байтов адреса, однако процес- сор будет считать его кодом операции, что приведет к абсолют- но непредсказуемым результатам (подробнее об этом см. статью 45). Как правило, все точки вашей программы, на которые мо- жет происходить переход, должны иметь метки. Единственным разумным исключением из этого правила является использова- ние команды типа BNE>(c-|-l5 (как в приведенном выше при- мере), когда между этой командой и точкой перехода имеется очень короткая последовательность команд1). В любом случае помните, что константа 5 выражает число байтов, а не число команд, как в некоторых других ЭВМ. Между прочим, машин- ный код команды BNE>|<-j-!5 всегда составляет D0 03. В нем используется относительный адрес 03, поскольку два байта, занимаемые самой командой BNE, не считаются (см. статью 27). » В языке ассемблера LISA 2.5 в таких случаях лучше пользоваться локальными метками. Для условного перехода на локальную метку Д1 можно написать: BNE>1.
110 Статья 31 Упражнения 1. Напишите предложение Бейсика, соответствующее при- веденной ниже последовательности предложений языка ассемб- лера (Р и Q — 16-разрядные величины): р Q DFS 12 DFS 12 LDA Р CLC ADC Q ТАХ LDA Р + 11 ADC Q+11 TAY INX BNE L4 INY L4 STX Q STY Q + I1 2. Напишите на языке ассемблера программу прибавления I к 3-байтовой величине Р с переставленными байтами. 3. Напишите на языке ассемблера программу вычитания 1 из 3-байтовой величины Р с переставленными байтами. Статья 31 Умножение и деление на два В десятичной системе счисления добавление нуля в конце числа равносильно умножению его на 10. В статье 3 уже отме- чалось, что добавление нуля в конце двоичного числа умножает его на 2; так, двоичное 11011, умноженное на 2, есть 110110. В регистре умножение на 2 производится путем сдвига влево всех битов, при этом в самый правый разряд всегда записыва-
Умножение и деление на два 111 ется 0 (рис. 31.1). Для выполнения этой операции предусмотре- на специальная команда ASL (Arithmetic shift left — арифмети- ческий сдвиг влево), сдвигающая содержимое регистра А. Рис. 31.1. Команда ASL умножит на 2 число со знаком, даже если оно отрицательно. ASL выполняет беззнаковое вычисление А=2>{«А, или А=А-|-А; но в статье 7 было показано, что операции зна- кового и беззнакового сложения производятся одинаково, если используется представление в виде дополнения до двух. Поэтому ASL выполняет также знаковое вычисление А=А4~А, или, что то же самое, А=2>(сА. Операция деления на 2 является обратной по отношению к умножению на 2. Деление осуществляется, очевидно, сдвигом вправо, при этом в самый левый разряд всегда записывается О (рис. 31.2). Деление на 2 в регистре А выполняет команда LSR (Logical shift right—логический сдвиг вправо). В отличие от ASL команда LSR не может быть использована для работы с отрицательными числами. Действительно, в ре- зультате сдвига командой LSR в знаковом разряде числа всегда будет появляться 0. Операции сдвига, которые всегда приводят к правильному умножению или делению, принято называть арифметическим сдвигом. В противоположность этому логиче- ский сдвиг всегда приводит к появлению 0 в крайнем разряде (команду ASL можно отнести и к той и другой разновидности сдвига). Обе команды, ASL и LSR, могут выполнять сдвиг не только в регистре А, но и непосредственно в памяти. Так, ASL Q умно- жает Q на 2; LSR Q делит величину Q (без знака) на 2. Можно даже сдвигать индексированную переменную, если использовать регистр X (но не Y). Так, ASL Т, X умножает T(J) на 2, если массив Т начинается с Т(0), a J загружено в регистр X. При тех же допущениях LSR Т, X разделит величину без знака Т (J) на 2.
112 Статья 31 Необходимо заметить, что процессор 6502 не допускает непос- редственного сдвига в регистрах X и Y. Обе команды, ASL и LSR, иногда используются для умно- жения или деления на 4, 8 и другие степени двух, для чего со- ответствующую команду нужно просто повторить необходимое число раз. Так, две команды ASL Q умножают Q на 4, посколь- ку (Q^<2) ;|<2=Q>|c4 (не пытайтесь, однако, умножить на 3 пу- тем двойного сдвига). Заметьте, что команды ASL Q и LSR Q, как и INC Q или DEC Q, не изменяют содержимого регистра А. Бит, который выдвигается из регистра или ячейки (влево при использовании ASL или вправо при использовании LSR), не теряется. Этот бит загружается в разряд переноса. Этим можно воспользоваться для того, чтобы с помощью команды LSR перейти, например, на метку ODD, если в регистре А не- четное число: LSR ; Выдвинуть бит младшего разряда в разряд переноса BCS ODD ; Если он имел значение 1, число было нечетным Здесь использован тот факт, что все нечетные двоичные числа оканчиваются битом, имеющим значение 1, в то время как чет- ные числа оканчиваются нулем (см. конец статьи 3). Команда ASL устанавливает флажок переноса обычным образом. Пусть Z — сдвигаемая величина. Если 2XZ<256, флажок переноса будет находиться в состоянии «сброшен». Это обстоятельство можно использовать для упрощения программирования. Напри- мер, в последовательности команд LDA Q ; Переслать Q в регистр А ASL ; Получить 2 Q в регистре А ADC Q ; 2*Q+Q=3*Q STA V ; V=3*Q нет необходимости выполнять CLC перед ADC, поскольку фла- жок переноса и так сброшен (в противном случае результат не- верен в любом случае, так как, если 2>|<Q не помещается в один байт, 3j)«Q тем более не поместится)1). Другой пример использования флажка переноса после ко- манды LSR—округление с избытком. Вообще выполнение LSR приводит к округлению с недостатком; так, если 21 разделить на 2 с помощью LSR, получится 10 (ближайшее целое, меньшее, чем 21/2, или 101/2). Если мы хотим выполнять округление с избытком (и получить в нашем примере 11, т. е. ближайшее це- >> При умножении величины Q со знаком на 3 команду GLC придется использовать, потому что при отрицательном Q команда ASL будет всегда устанавливать разряд переноса.
Умножение и деление на два 11$ лое, большее, чем результат операции), мы должны использо- , вать ADC^IO после LSR. Эта команда прибавит 0, если в ре- гистре А было четное число (и, следовательно, произошло деле- ние без остатка); если же в регистре А было нечетное число, прибавится 1. Флажки нуля и знака также устанавливаются командами * ASL и LSR обычным образом: они фиксируют нулевой или от- рицательный результат сдвига (применение этого см. в упр. 1 статьи 35). В некоторых процессорах предусмотрены команды сдвига, выполняющие сдвиг на несколько разрядов. Отсутствие таких команд в процессоре 6502 является очень незначительным не- удобством, так как команду сдвига на 1 разряд всегда можно- повторить нужное число раз. Упражнения 1. Напишите предложения Бейсика, соответствующие при- веденным ниже последовательностям предложений языка ас- * сем б лера: >}с a) LDA Q ASL ' ASL ADC R j STA Р б) LDA G SEC SBC Н LSR STA F * в) LDA Bl ASL ASL ASL F STA B2 2. Что произойдет с содержимым регистра А в результате- выполнения последовательности команд LSR и ASL? 3. Что неверно в следующей программе, которая должна выполнять вычисления S=T(2;|<M) массив Т начинается с Т(0))? 8—589
114 Статья 32 LDX М ASL X LDA T, X STA S Статья 32 Массивы двухбайтовых величин Существуют два способа хранения в памяти массивов 16-раз- рядных, или 2-байтовых величин. Первый способ заключается в том, что мы просто организуем в памяти два массива. Если, например, нам нужен массив, состоящий из ста 2-байтовых ве- личин от Т(0) до Т(99), мы можем организовать два массива, каждый размером в 100 байт, с именами UPPERT или LOWERT (с помощью псевдокоманд UPPERT DFS 1100 и LOWERT DFS 1100). Например, нам требуется выполнить переход на мет- ку LESS, если T(J)<T(K). В этом случае необходимо написать следующую программу (используя процедуру 16-разрядного сравнения из статьи 29): LDX J ; Загрузить индекс J LDY К ; Загрузить индекс К LDA UPPERT, X ; Сравнить старшее полуслово T(J) СМР UPPERT, Y ; со старшим полусловом Т(К) BNE DECIDE ; Если равны, LDA LOWERT, X ; сравнить младшее СМР LOWERT, Y ; полуслово T(J) DECIDE ВСС LESS ; с младшим полусловом Т(К) Массивы LOWERT и UPPERT иногда называют параллель- ными массивами. Другой способ заключается в создании одного массива Т (с помощью Т DFS 1200), организованного так, как показано на рис. 32.1. В этом случае, однако, нам придется использовать регистры X и Y несколько иначе. Для того чтобы понять, почему
Массивы двухбайтовых величин 115 это так, предположим, что в регистр X загружено 3. Посмот- рим снова на рис. 13.1. Адрес Т(3) определялся как адрес Т(0) плюс 3; однако из рис. 32.1 видно, что к адресу Т(0) надо при- бавить 6, а не 3. Таким образом, в последовательном массиве, изображенном на рис. 32.1, в индексный регистр надо загружать, не значение индекса, а число, в два раза большее. Переменная Адрес Т(0) (младшее полуслово) 0858 =0858+2>|<0 Т(0) (старшее полуслово) 0859 =0858+2sf= 0-J-1 Т(1) (младшее полуслово) 085А=0858+ 2>|«1 Т(1) (старшее полуслово) 085В=0858+ 2>^14-1 Т(2) (младшее полуслово)085С=08584-2^2 Т (2) (старшее полуслово) 085D=08584-2^24-1 Т(3) (младшее полуслово)085Е=0858+24;3 Т (3) (старшее полуслово) 085F=08584-2^3+1 Т (4) (младшее полуслово) 0860 =0858+2>|<4 Т(4) (старшее полуслово) 0861 =08584-2^44-1 Т(5) (младшее полуслово)0862 =0858+2^5 Т(5) (старшее полуслово)0863=08584-2>J<54-l Т(99) (младшее полуслово) 091Е = 0858-|-2>|<99 Т(99) (старшее полуслово)091Е=0858-|-24<994-1 Рис.32.1. Адреса элементов массива 2-байтовых величин. Для этого мы сначала загрузим индекс в регистр А; затем умножим его на два с помощью ASL (см. предыдущую статью), а затем перешлем результат в регистр X (или Y). Это дает нам возможность использовать, например, команду LDA Т, X для загрузки в регистр А младшего полуслова Т (J). Для загрузки старшего полуслова, адрес которого на 1 больше, можно напи- сать LDA T-f-H, X (используя смещение, описанное в статье 23). Программа перехода на LESS при T(J)<T(K) будет в этом, случае выглядеть следующим образом: LDA J ; Загрузить индекс J ASL ; Умножить его на 2 ТАХ ; Переслать в регистр X LDA К ; Загрузить индекс К 8*
Я16 Статья 32 ASL Умножить его на 2 TAY Переслать в регистр Y LDA Т+!1,Х Сравнить старшее полуслово T(J) СМР T+!1,Y со старшим полусловом Т(К) BNE DECIDE Если равны, LDA T, X сравнить младшее CMP T, Y полуслово Т(J) с DECIDE BCC LESS младшим полусловом Т (К) Если последовательный массив Т начинается не с Т(0), а с Т(1), требуется еще одно смещение. Поскольку в таком мас- сиве отсутствуют первые два байта массива (а не один, как в статье 23), для обращения к T(J) надо использовать адрес Т—!2 (а не Т—!1). В этом случае последние шесть команд пре- дыдущей программы будут такими: LDA Т—!1,Х ; Сравнить старшее полуслово T(J) СМР Т—11, Y ; со старшим полусловом Т(К) BNE DECIDE ; Если равны, LDA Т—!2, X ; сравнить младшее СМР Т—!2, Y ; полуслово Т(J) с DECIDE ВСС LESS ; младшим полусловом Т(К) Здесь Т—!1 получилось после вычисления Т+!1 —!2. Индексы-константы последовательных массивов 2-байтовых величин также должны умножаться на два. Так, в' последова- тельном массиве Т, начинающемся с Т(0), элемент Т(3) хра- нится не по адресу T-J-I3, а по адресам T-f-!6 и T-f-17. Далее, если индекс такого массива включает константу в качестве сла- гаемого или вычитаемого, эта константа должна быть умножена на 2. Например, следующая программа устанавливает W рав- ным T(N-]-3): LDA ASL N ; Загрузить индекс N ; Умножить его на 2 ТАХ ; и переслать в регистр X LDA Т+16, X ; Адрес равен (T + 6) + (2>J<N), STA W ; что совпадает с T-|-2:4i (N+3), LDA Т+!7, X ; это устанавливает STA W+I1 ; W=T(N + 3) Здесь, конечно, также нельзя было написать T-f-13. В примерах этой статьи мы использовали оба индексных ре- гистра (X и Y), потому что это было необходимо. Вообще же
Умножение на десять 117 вы должны стараться пользоваться минимальным числом реги- стров (не ухудшая, однако, характеристик программы в смысле требуемой памяти и скорости выполнения). Такой подход по- зволит вам в случае длинных программ не испытывать недо- статка в регистрах. Упражнения 1. Напишите программы на языке ассемблера, соответству- ющие приведенным ниже предложениям Бейсика. Считайте, что массив Т последовательный и начинается с Т(0), а В—16-раз- рядная величина: а) В=Т(5); б) Т(К)=В; в) В=В4-Т(К-3). 2. Напишите программы на языке ассемблера, соответствую- щие предложениям Бейсика из упр. 1, считая, что массив Т хранится в виде двух параллельных массивов LOWERT и UPPERT, начинающихся с LOWERT (1) и UPPERT (1). В — по- прежнему 16-разрядная величина. 3. Напишите программу на языке ассемблера для перехода на LESS по условию T(J)<T(K). Считайте, что Т — последо- вательный массив 24-разрядных величин, начинающийся с Т(0), причем байты каждого элемента записаны в нормальном по- рядке (т. е. не переставлены). Не используйте цикл. (Подсказка: в этом случае нельзя умножить индексы J и К на 2 с помощью ASL. Почему?) Статья 33 Умножение на десять В процессоре 6502 не предусмотрено команд для перемно- жения двух произвольных чисел. Умножение производится с помощью подпрограмм (см. статьи 39 и 40). Однако содержи-
118 Статья 33 мое регистра А можно умножить на 10 с помощью следующих. 5 команд: ASL ; Получить в А величину 2*А’> STA TEMP ; Записать 2*А в TEMP ASL ; Теперь умножить первоначальное ASL ; содержимое А на 8 ADC TEMP ; 8*A-f-2*A= 10*А В этой программе величина 2*А была записана в ячейку TEMP для временного хранения с целью использования в другом ме- сте программы. Такие «временные ячейки» часто используются для хранения промежуточного результата в процессе несложных вычислений, если после завершения вычислений этот промежу- точный результат уже не нужен. Заметьте, что перед ADC не- было необходимости использовать CLC (если мы работаем с числами без знака). Этот вопрос был рассмотрен в конце статьи 31. Предположим теперь, что у нас есть целое число N, имеющее значение между 0 и 255, выраженное в символьной форме и хранящееся где-то в символьном массиве В. Мы хотим вычис- лить N и записать его в регистр Y. Символьные коды цифр, об- разующих N, расположены последовательно, начиная с B(J) (J находится в регистре X), и заканчиваются каким-либо сим- волом, не являющимся цифрой. Если, например, N состоит из- двух цифр, то их символьные коды хранятся в B(J) и B(J-f-l), a B(J+2) уже не является кодом цифры. Заметьте, что мы не знаем заранее, из скольких десятичных цифр состоит N — одной, двух или трех. Процедура преобразования будет выглядеть сле- дующим образом: 1) очистить N; 2) если В (J) не является символьным кодом цифры, прекра- тить вычисления; 3) преобразовать B(J) в соответствующую цифру D (если, например, в B(J) записан код цифры «7», т. е. шестнадцатерич- ное В7, то D=7). См. табл. П9; 4) вычислить N=10*N+D (если, например, N=15, а D=4, то N становится равным 154, так как 10*N4-D = = 10* 154-4= 1504-4= 154); 5) увеличить J на 1 (чтобы получить в J индекс следующего элемента массива В) и вернуться к шагу 2. Приведенная ниже программа выполняет описанную проце- дуру. Заметьте, что шаги 2 и 3 в программе объединены. Мы 1> В комментариях к программам мы будем пользоваться сокращенными обозначениями регистров: А — регистр А, X — регистр Хит. д.— Прим, перев.
Умножение на десять 119 проверяем условие B(J)>„9” (точнее говоря, условие B(J)>„9”+1). Если условие выполняется, вычисления прекра- щаются (шаг 2). Если нет, флажок переноса сброшен (в про- тивном случае мы бы перешли на метку DONE). Теперь мы хотим вычесть „О” — код символа 0 и тем самым преобразовать В (J) в D (шаг 3; в шестнадцатеричной форме это представля- ется так: В7—В0=07). Поэтому мы вычитаем „О” минус 1, но без займа (поскольку мы знаем, что разряд переноса сброшен, а это указывает на состояние займа); в результате мы вычи- таем „О”, не выполняя предварительно команду SEC и экономя тем самым одну команду. Если B(J) было меньше, чем „О”, то при вычитании выполняется заем и сбрасывается флажок пере- носа; в результате мы опять переходим на метку DONE. Если B(J) не меньше, чем „О”, и не больше, чем „9”, то B(J), очевид- но, является символьным кодом цифры, и мы переходим к ша- гу 4. (Другое практическое применение описанного алгоритма рассмотрено в статье 62.) ORG $0860 LDY #10 CONV LDA В, X DONE D СМР #"9" +11 BCS DONE SBC #"0"—11 ВСС DONE STA D TYA ASL STA TEMP ASL ASL ADC TEMP ADC D TAY INX JMP CONV ORG $0920 DFS 11 TEMP DFS 11 В DFS 1256 ; Начало программной секции ; Начальное значение N в Y ; Получить следующий символ ; Код больше "9"? ; Да, конец числа ; Преобразовать символьный код ; в двоичное значение D (если ; D<0 — конец числа) ; Переслать N (из Y) в А ; Пять команд ; >|<>^>|<>|< из текста вначале sfofofofc ; >|с>|< этой статьи ; (умножение А >[<>|<>|<>]< ; на 10) ; A=10>j<N+D (CLC не требуется) ; N=10*N+D ; Инкремент индекса символа ; Получить следующий символ (следующая команда) ; Начало секции данных ; Текущая цифра ; Временное хранение 2>J<Y ; Массив символов
120 Статья 33 Закончив обработку символа B(J), мы переходим к следующему символу В(J-J-1), выполнив J=J~H или (поскольку J хранится в регистре X) произведя инкремент X. Заметим, что в програм- ме было выполнено назначение регистру Y величины N (см. статью 21). Умножение на целую константу, отличную от 10, произво- дится аналогично и, между прочим, никогда не требует CLC, так как, если результат помещается в один байт, флажок пере- носа в процессе умножения всегда будет сброшен. Упражнения 1. Напишите предложения Бейсика, соответствующие при- веденным ниже последовательностям предложений языка ас- семблера. Ваши предложения Бейсика не должны содержать знаков плюс. * а) LDA Т CLC ADC Т ADC Т STA Т ASL Т б) LDA V STA U ASL ASL ADC U ASL ADC U STA U * в) LDA G ASL ADC G ASL ASL ADC G STA F 2. Рассмотрите приведенную ниже программу. Выполняет ли она деление содержимого регистра А на 10? Если нет, что же получается в регистре А?
Циклический и 16-разрядный сдвиги 121 LSR STA TEMP LSR LSR CLC ADC TEMP 3. Рассмотрите такую последовательность команд: LDA в, X ; Получить следующий символ СМР ; Код больше «9»? BCS DONE ; Да, конец числа SBC #"0"—11 ; Вычесть «0» с переносом вес DONE ; Если меньше 0, конец числа Эта последовательность, как было описано выше, проверяет, является ли следующий символ цифрой (от „О” до „9” включи- тельно), и преобразует символьный код в двоичное значение. Можно ли написать аналогичную по смыслу последовательность из 5 команд, в которой команда SBC будет стоять перед СМР? Если да, какие константы надо использовать? Если нет, по- чему? Статья 34 Циклический и 16-разрядный сдвиги Пусть нам надо умножить на два 2-байтовое (16-разрядное) число без знака. Можем ли мы просто сдвинуть влево оба бай- та? Здесь возникает некоторое затруднение. Как видно из рис. 34.1, все 16 бит сдвинулись правильно, за исключением од- ного в середине, который должен перейти из младшего полу- слова числа в старшее. Сдвинем сначала младшее полуслово. Тогда этот бит, левый бит младшего полуслова, загружается в разряд переноса (см. статью 71). Теперь нам нужна команда, которая сдвинет влево
122 Статья 34 старшее полуслово и при этом перешлет признак переноса в правый разряд старшего полуслова. Такая команда существует, она имеет мнемонику ROL (Rotate left — циклический сдвиг влево). Как и ASL, команда ROL пересылает содержимое левого разряда данного регистра или ячейки памяти (назовем ее z) в разряд переноса. Мы можем рассматривать z вместе с разрядом переноса как 9-разрядное кольцо, при этом команда ROL пере- мещает биты по этому кольцу, как это показано на рис. 34.2 (отсюда и название «циклический» сдвиг). Команду ROL можно использовать и с регистром А, и с ячей- ками памяти, и с индексированными (через регистр X) перемен- Разряд z переноса Рис. 34.2. ными, как это уже описывалось применительно к команде ASL. Для того чтобы сдвинуть влево на 1 разряд 16-разрядное число М, младшее полуслово которого, как обычно, хранится по адре- су М, а старшее—по адресу М+!1, мы должны написать ASL М ; 16-разрядный ROL М4-11 ; сдвиг влево Такой сдвиг часто называют двойным, сдвигом,. Аналогичное затруднение возникает при сдвиге 16-разрядного числа вправо, как это показано на рис. 34.3. Как и раньше, здесь один бит сдвигается не так, как надо. Если мы сдвинем сначала старшее полуслово, тогда этот бит (правый бит старшего полу- слова) перейдет в разряд переноса. Теперь нам нужна команда, которая сдвинет вправо младшее полуслово и к тому же пере- шлет признак переноса в левый разряд младшего полуслова. Это команда ROR (Rotate right — циклический сдвиг вправо), которая действует аналогично команде ROL (рис. 34.4). Как и ROL, команду ROR можно использовать и с регистром А, и с
Циклический и 16-разрядный сдвиги 123 ячейками памяти, и с индексированными (через регистр X) пе- ременными. Сдвиг взятой нами для примера 16-разрядной вели- чины М. вправо на 1 разряд производится следующим образом: LSR М+!1 ; 16-разрядный ROR М ; сдвиг вправо Как и раньше, мы использовали двойной сдвиг. Старшее полуслово Рис. 34.3. Младшее полуслово Команду ROR можно использовать для деления на 2 величи- ны со знаком, если первоначальное значение знакового разряда было сохранено в разряде переноса с помощью команды ASL. 6= Рис. 34.4. Разряд переноса Так, для того чтобы разделить содержимое регистра А, рассмат- риваемое как величина со знаком, на 2, можно написать ТАХ ; Сохранить А в X ASL ; Левый бит переслать в разряд переноса ТХА ; Восстановить А из X ROR ; Знаковый сдвиг вправо Команда ROR сдвинет содержимое регистра А вправо, но левый разряд останется тем же, что и был: если в нем был 0, то и оста- нется 0; если была 1, останется 1. Это важно при делении на 2, так как если z положительно, то и z/2 положительно, а если z отрицательно, то и z/2 отрицательно. Рассмотренный сдвиг часто называют сдвигом с расширением знака, поскольку знако- вый разряд не только сохраняется, но расширяется вправо1). о Любой сдвиг с расширением знака является арифметическим, посколь- ку он правильно осуществляет операцию деления. В некоторых ЭВМ пре- дусмотрена специальная команда для выполнения сдвига с расширением анака.
124 Статья 34 Команды ROL и ROR (так же как и команды ASL и ASR) устанавливают флажки нуля и знака обычным образом, как уже описывалось в конце статьи 31. Обратите внимание на сохра- нение в приведенной .выше программе содержимого регистра А в регистре X с последующим восстановлением (т. е. обратной пересылкой из X в А). Это весьма полезный прием, если, ко- нечно, регистр X (или Y) свободен. Упражнения 1. а) Напишите программу на языке ассемблера для вычи- сления N = 10>|<N-|-D, где N и D — 2-разрядные величины с пе- реставленными байтами. (Подсказка: используйте рассмотрен- ную процедуру умножения на 10, но замените 8-р азрядные сдвиг и сложение на 16-разрядные.) б) Напишите программу на языке ассемблера для сдвига 24-разрядной величины Р с переставленными байтами влево на 1. в) Напишите программу на языке ассемблера для сдвига N-разрядной .величины Р (где N — переменная) с переставлен- ными байтами вправо на 1. Используйте цикл. + 2. Складывая в столбик двухразрядные числа на бумаге, например 12 14 39 +27 92 мы обычно сначала складываем все младшие разряды (2+4+ +9+7 в данном случае), а затем все старшие разряды. В ЭВМ, однако, складывая несколько 2-байтовых чисел, мы обычно при- бавляем каждое следующее число к полученной ранее частич- ной сумме; и, во всяком случае, мы не можем сначала сложить все правые байты, а затем — все левые. Почему? (Подсказка: подумайте, что происходит с разрядом переноса.) 3. Желая сдвинуть 2-байтовое число М .влево на две пози- ции, мы можем воспользоваться приведенной ниже програм- мой а) и не должны использовать программу б): a) ASL М б) ASL М ROL М+!1 ASL м ASL М ROL М+!1 ROL М+11 ROL М+!1 Объясните, почему программа б) неверна.
Обработка битов и паритет 125» Статья 35 Обработка битов и паритет Используя цикл, можно просмотреть все байты в массиве. Точно так же можно в цикле просмотреть и все биты в одном байте. Просмотр можно организовать как слева направо, так в справа налево. Проще всего использовать для этого сдвиг и анализ разряда переноса. С каждым сдвигом очередной бит выдвигается из байта и поступает в разряд переноса, где анализируется. Для просмотра всего байта сдвиг повторяется 8 раз1). Пусть, например, нам надо определить количество установ- ленных разрядов (т. е. разрядов, содержащих 1) в регистре А. Это можно сделать следующим образом (производя просмотр» слева направо): LDX #!8 В счетчике цикла 8 LDY #!0 В счетчике битов 0 СВ ASL Сдвинуть бит в разряд переноса ВСС СВ1 В разряде переноса 0? INY Если нет, инкремент счетчика битов СВ1 DEX Декремент счетчика цикла BNE СВ Если не нуль, повторить Можно выполнять просмотр и справа налево; для этого доста- точно заменить LSR на ASL. Паритет байта равен 1 (или нечетен), если байт содержит нечетное число установленных битов. В противном случае па- ритет равен 0 (или четен). Как уже отмечалось в статье 3, не- четные двоичные числа заканчиваются единицей, четные—ну- лем. Отсюда следует, что паритет (0 или 1) байта, анализируе- мого приведенной выше программой, указывается в младшем разряде счетчика битов. Таким образом, программу можно ис- пользовать для определения паритета содержимого регистра А,, если дополнить ее строками TYA ; Загрузить в А счетчик битов LSR ; Проверить младший бит BCS ODDP ; Если он равен 1, паритет нечетен •>. 9 раз, если анализируемый байт надо вернуть в исходное состояние.— Прим, перев.
126 Статья 35 или им подобными. Мы вернемся к рассмотрению вопроса об использовании паритета в статье 90. Рассмотрим еще пример. Пусть нам надо преобразовать байт Q в форму, пригодную для вывода, т. е. в строку из 8 байтов, каждый из которых содержит символьный код нуля или едини- цы, т. е. „0” или „1”. Если мы предполагаем выводить эти бай- ты программой COUT по мере их формирования, то преобразо- вывать биты числа Q надо слева направо и использовать для этого сдвиг влево. Это можно сделать следующим образом: LDA Q Переслать Q во временную STA TEMP ячейку для сдвига LDX #18 В счетчике цикла 8 OCONV ASL TEMP Получить следующий бит LDA #"0" Предположим, что бит устан. в 1 BCC OCONV1 Действительно установлен? LDA #"1" Если нет, установить бит в 1 OCONV1 JSR COUT Вывести "0" или "1" DEX Декремент счетчика цикла BNE OCONV Если не 0, повторить Используя технику работы с битами, можно составить байт из 8 бит. Для этого каждый очередной бит загружается в раз- ряд переноса, а затем вдвигается в байт командами циклическо- го сдвига (ROL или ROR). Пусть, например, имеется 8-байтовый массив с именем BITS, в каждом байте которого содержится либо „0”, либо „1”. Заметьте, что в этом случае младший бит каждого байта будет иметь значение 0 или 1 в зависимости от того, содержит ли весь байт „0” или „1”. Преобразование этих байтов в двоичное число Q, которое они выражают, выполняется •следующей программой: LDX #!0 CBYTE LDA BITS.X LSR ROL Q INX СРХ #!8 BNE CBYTE ; В счетчике цикла 0 ; Загрузить очередной символ ; Правый бит в разряд переноса ; Сдвинуть этот бит в Q ; Инкремент счетчика цикла ; Сравнить с максимумом ; Если не равно, повторить Обработка битов используется в операциях умножения и деления, которые будут рассмотрены в статьях 38—40. Во второй программе этой статьи используется прием, часто позволяющий сэкономить команду. Мы хотим загрузить в ре- гистр А „0” или „1” в зависимости от состояния флажка пере-
Обработка битов и паритет 127 носа. Если мы сначала проверяем флажок переноса, то требу- ется 4 команды: BCS LDA JMP OCONV1 LDA OCONV2 OCONV1 ; Флажок переноса сброшен ; или установлен? #"0" ; Сброшен, загрузить «О» OCONV2 ; и перейти вперед #"1" ; Установлен, загрузить «1» (следующая команда) Здесь можно было сэкономить один байт, заменив JMP на ВСС, поскольку флажок переноса должен быть сброшен (этот прием был упомянут в конце статьи 27). Однако можно сделать еще лучше, полностью устранив переход (см. вторую программу этой статьи): сначала загрузить „О”, затем проверить флажок переноса и, наконец, загрузить „1”, если флажок переноса уста- новлен. Заметьте, в этой программе нужно определить COUT (с помощью COUT EQU $FDED), как и раньше. Упражнения 1. Рассмотрите предлагаемую оптимизацию первой про- граммы этой статьи: LDY #!0 Инициализировать счетчик ASL Левый бит А в разряд переноса СВ ВСС СВ1 Бит установлен в 1? INY Если да, инкремент счетчика СВ1 ASL Перейти к следующему биту BNE СВ Если больше битов нет, закончить Здесь используется то обстоятельство, что команда ASL уста- навливает флажок нуля. Если k правых битов регистра А — ну- ли, то мы экономим k последних шагов цикла, поскольку ко- манда BNE не выполняет переход. Однако в программе есть ошибка. Найдите ее. (Подсказка: испытайте числа $С0, $80 и 0 в качестве начального содержимого регистра А.) 2. Во второй программе этой статьи вместо возможных че- тырех команд использованы три: LDA #"0" ВСС OCONV1 LDA #"1" (см. обсуждение в тексте). Этот фрагмент можно еще улуч- шить, заменив три команды на две, из которых первая загру-
128 Статья 36 жает в регистр А константу, вторая есть ROL. Какую константу здесь надо использовать и почему такое улучшение возможно? >{< 3. Модифицируйте третью программу этой статьи, используя инкремент регистра X (INX) вместо декремента (DEX). За- метьте, что в этом случае LDA BITS.X следует заменить на LDA BITS—!1,Х. Почему? Далее, какое важное изменение надо <будет внести в метод составления байта? Статья 36 Просмотр таблиц и вычисление времени выполнения программы Паритет байта (см. первую программу предыдущей статьи) 1иожно определить другим способом во много раз быстрее: ТАХ LDA PARITY, X BNE ODDP Здесь PARITY — 256-байто.вый массив, причем для каждого воз- можного байта J, т. е. для каждого J из диапазона 0 — 255, PARITY (J) =0, если паритет J четен; в противном случае PARITY (J)=l. Такой массив часто называют таблицей (в данном случае таблицей на 256 входов), а метод, использованный в приведен- ной программе, называют просмотром таблицы. В качестве дру- гого примера использования просмотра таблицы можно привести методику деления целого числа Q на 10 с получением целочис- ленного ответа (когда, например, 85, деленное на 10, дает не 8'/2, а 8 и 5 в остатке). Если у нас есть таблица DIVIO с 256 входами и DIV10(J) =J/10 (в указанном выше смысле) для каждого возможного J, то разделить Q на 10 можно двумя командами: LDX LDA Q DIVIO, X
Просмотр таблиц и вычисление времени выполнения программы 129 Серьезный недостаток метода просмотра таблиц — большие затраты памяти. 256 ячеек — это заметная величина, особенно для небольших ЭВМ. Иногда, однако, таблицы оказываются не такими объемными. Если, например, мы хотим умножать на 10 с использованием таблицы MULT10, в которой MULT10(J) = — 10>|cJ, то J будет изменяться здесь от 0 до 25, так как 26^с10 (=260) уже не уместится в одном байте. Поэтому таблица в этом случае будет иметь только 26 входов. Поскольку программы, использующие просмотр таблиц, эко- номят значительное время, интересно определить численную ве- личину экономии. В процессоре 6502 время выполнения каждой команды определяется числом циклов. Данные о числе циклов для каждой команды приведены в табл. П4Ч Машинный цикл процессора 6502 длится приблизительно 1 мкс, или одну милли- онную долю секунды, так что за 1 с выполняется приблизитель- но 1000000 циклов (частота циклов составляет; таким образом, 1 МГц). Более точное значение— 1023000 циклов в секунду. Некоторые вычислительные системы, использующие процессор 6502, работают с частотой 2000000 циклов в секунду, т. е. 2 МГц * 2>. Из табл. П4 можно найти, что: 1) команда ТАХ требует 2 цикла; 2) команда LDA PARITY,X требует 4 цикла (не будем пока принимать в расчет возможности адресации к нулевой страни- це— операнды Z или Z,X или Z.Y в табл. П4 — об этом речь будет идти позже, в статье 74); 3) команда BNE ODDP требует 2 цикла (если только не выполняется переход). Всего, таким образом, требуется 8 циклов (или на один боль- ше, если выполняется переход). В действительности может по- требоваться на два цикла больше, если BNE осуществляет пере- ход по адресу, старший байт которого отличается от старшего байта адреса самой команды BNE. Точно так же дополнитель- ный цикл может потребоваться для индексных команд, если прибавление 8-разрядного индекса к 16-разрядному адресу из- меняет старший байт этого адреса. В дальнейшем, однако, мы не будем принимать в расчет эти добавки, так как в случае крайней необходимости всегда можно написать программу так, чтобы их не было. *> Таблицу 4 можно также использовать для определения числа байтов в заданной команде. Например, для команды LDA Q в табл. П4 дается ма- шинная форма AD cd ab-, таким образом, в ней 3 байта (AD, cd и ab). 2> Можно возражать против приведенных чисел, указывая, что 1022700 циклов в секунду является более точным значением; однако эта величина может изменяться в определенных пределах. Используемое нами всюду чис- ло 1023000 указано в справочном руководстве к ЭВМ Эпл. 9—589
130 Статья 36 Сравним теперь рассматриваемую программу с программой определения паритета из статьи 35 (первая программа этой статьи с тремя дополнительными командами — TYA, LSR и BCS). 1. Сначала рассмотрим команды, входящие в цикл. Их 5 (ASL, ВСС, INY, DEX, BNE). Если ни ВСС, ни BNE не выпол- няют переход, каждая из команд требует 2 цикла, что дает в сумме 10 циклов. 2. Команда BNE фактически выполняет переход в каждом шаге, кроме последнего. Получается 11 циклов. 3. Если выполняет переход команда ВСС, требуется еще один цикл, но тогда не выполняется команда INY, что уменьшает время на 2 цикла, давая в сумме 10 циклов. Таким образом, каждый шаг программного цикла требует 10 или 11 машинных циклов. 4. Поскольку программный цикл выполняется 8 раз, мы имеем полное время от 10^8 до 11>|с8, т. е. от 80 до 88 машин- ных циклов. 5. Из полученного числа надо вычесть 1, поскольку команда BNE не выполняет переход в последнем шаге цикла. С уче- - том этого суммарное время лежит в пределах от 79 до 87 циклов. 6. Наконец, надо добавить еще 10 циклов на выполнение 5 команд (LDX, LDY, TYA, LSR, BCS), расположенных вне программного цикла. .Окончательный итог колеблется от 89 до 97 циклов плюс еще один, если команда BCS выполняет пере- ход. Заметьте, что команды загрузки (LDA, LDX или LDY) тре- буют только двух циклов, если загружается константа. Чрезвычайно высокая скорость работы ЭВМ является одним из секретов их необычайных возможностей. Заметьте, что рас- сматриваемый программный цикл, даже если он выполняется 8 раз, все же занимает менее 100 мкс, и, следовательно, вся программа может быть повторена более десяти тысяч раз в се- кунду! Мы говорим, что такая программа требует для выполне- ния менее 0,1 мс (1 мс=0,001 с). Никогда не забывайте при оценке времени выполнения про- граммы учесть фактическое число шагов в цикле. Если коман- ды, стоящие в программном цикле, требуют в сумме п машин- ных циклов, и весь цикл выполняется k раз, это дает nk машин- ных циклов (фактически почти всегда nk—1, так как последняя команда цикла, являясь обычно командой условного перехода, в последнем шаге цикла выполняется на 1 машинный цикл бы- стрее). Кроме того, всегда имеются команды, не входящие ни в один из циклов и выполняемые только один раз; требуемое для их выполнения время надо добавить к общему итогу.
Компромисс между объёмом памяти и временем выполнения 131 Упражнения 1. Заполнять длинную таблицу с помощью определений кон- стант— утомительное занятие. Так, например, чтобы заполнить таблицу с 26 входами для умножения на 10, надо 26 раз ис- пользовать определение (BYT!0, BYTI10, BYTI20 и т. д.). Можно поступить иначе — таблицу MULT10 заполнить требуемыми чи- слами программно. Это можно сделать один раз где-нибудь в начале программы, до первого использования таблицы. Напи- шите на языке ассемблера строки программного заполнения таблицы MULT10 (используйте команду DEX). 2. Определите время (в машинных циклах), требуемое для выполнения программы из статьи 21. У вас должна получиться формула, включающая N. Определите также полный размер программы в байтах. 3. Определите время (в машинных циклах), требуемое для выполнения второй программы на языке ассемблера из статьи 22. У вас должна получиться формула, включающая N. Определите также полный размер программы в байтах. Заметь- те, что эта программа дает тот же конечный результат, что и программа из статьи 21. Является ли она более оптимальной? Статья 37 Компромисс между требуемым объемом памяти и временем выполнения Существует много примеров того, что одна из двух программ, дающих одинаковый конечный результат, требует меньше вре- мени для выполнения, но занимает в памяти больше места. Та- кое противоречие — обычная ситуация. Иногда экономия памяти для нас важнее, чем сокращение времени .выполнения; в других случаях наоборот. Что выбрать в каждом конкретном случае? Для решения этого вопроса нам надо принять во внимание целый ряд обстоятельств. 1. О какой конкретно экономии времени или памяти идет речь. Если одна программа требует памяти в два раза больше, 5*
132 Статья 37 чем другая, но выполняется в 10 раз быстрее, время выполне- ния может оказаться более важным фактором. Если программа выполняется в два раза медленнее, но занимает в 10 раз меньше памяти, более важным фактором может оказаться память, тре- буемая программой. 2. Как часто выполняется программа. Если рассматривае- мый фрагмент входит в цикл или в подпрограмму, то сэконом- ленное время выполнения умножается на число выполнений цикла и число вызовов подпрограммы. Этот эффект усилива- ется, если цикл или подпрограмма сами входят в цикл или другую подпрограмму. 3; Каков общий объем доступной памяти. Не все ЭВМ Эпл имеют память максимального объема (65536 байт); в некото- рых вычислительных системах число байтов оперативной памя- ти очень невелико. В таких случаях память оказывается весьма ценной; вам, вероятно, придется экономить память всюду, где только возможно. 4. Взаимодействует ли ваша программа с другими устрой- ствами, имеющими собственные временные ограничения. Такие программы называются программами реального времени; иногда (хотя не всегда) важнейшим требованием для их нормального функционирования является максимальная скорость выпол- нения. 5. Платите ли вы за машинное время. При работе на боль- ших ЭВМ вы часто платите за использованное время. В этих случаях очень важно максимально сократить время выполнения программы. При использовании микроЭВМ машинное время обычно столь дешево, что может считаться бесплатным. Поэто- му чаще всего надо эконо'мить не время, а память. В качестве другого примера сопоставления расхода времени и памяти рассмотрим такую возможность деления Q на 10: LDA Q ; Установить А=0, а в счетчик числа LDX #$FF ; вычитаний 10 занести —1 SEC DTEN 1NX SBC #110 BCS DTEN ТХА ; Установить флажок переноса для вычитания ; Увеличить содержимое счетчика и ; вычесть 10. Если флажок переноса установлен ; (результат еще положителен), повторить ; Содержимое счетчика (А/10) в регистр А Эта программа занимает 12 байт памяти в отличие от 262 байт (256+6), расходуемых программой деления Q на 10 из преды- дущей статьи. Она требует от 16 до 191 машинных циклов (в среднем 103*/2 цикла) в отличие от 8 циклов. Таким образом,, эта программа почти в 13 раз медленнее, но более чем в 21 раз короче предыдущей программы. i
Компромисс между объемом памяти и временем выполнения 133 В действительности все эти числа не очень точные. В рас- смотренной только что программе команда ТХА часто опуска- ется; вместо этого содержимое регистра X непосредственно за- писывается в память с помощью команды STX. Далее, среднее число циклов (1ОЗ’/2) определено в предположении, что делятся случайные числа1). Практически, однако, обычно получается так, что маленькие числа делятся гораздо чаще больших. В этом случае скорость выполнения программы существенно возрастет. Поскольку такого рода неопределенности являются скорее пра- вилом, чем исключением, в практическом программировании редко вычисляют время выполнения программы, хотя такого рода расчеты представляют значительный теоретический ин- терес. Иногда изучение двух конкурирующих программ показыва- ет, что никакой компромисс и не требуется: одна из рассматри- ваемых программ оказывается одновременно и короче и быстрее другой. Например, программа, приведенная в конце статьи 22 быстрее и короче эквивалентной программы из статьи 21. Есть еще одна величина, которую надо принимать в расчет при оптимизации программы. Это — время программиста. Про- грамма, требующая много времени на написание и отладку, может оказаться очень дорогой, даже если она расходует мень- ше машинного времени и занимает меньше места в памяти, чем какая-то другая, в целом эквивалентная ей программа. По этой причине распространена практика написания простых и понят- ных, но относительно медленных программ (для них имеется жаргонное выражение quick and dirty programs — «скорые, но грязные» программы). Если программу предлагается выполнить всего один-два раза, или если ее придется часто модифициро- вать, метод «скорых, но грязных» программ почти всегда дает наилучшие результаты. Упражнения >(< 1. Пусть некоторая программа на Бейсике выполняется на одной ЭВМ в несколько сот раз быстрее, чем на другой. Можно ли представить условия, при которых вторая ЭВМ все же пред- почтительнее первой для выполнения данной программы? 2. Сравните программу ТАХ LDA MULT10, X (здесь MULT10 — такой же массив, как и в статье 36) с первой программой из статьи 33, которая дает тот же конечный резуль- *> Правильнее было бы сказать не случайные, а равномерно распределен- ные числа.— Прим, перев.
134 Статья 38 тат. Насколько они отличаются по требуемому объему памяти и времени выполнения? При расчете не забудьте в обоих слу- чаях учесть байты программных строк и байты данных. 3. Сравните между собой две программы, приведенные в на- чале статьи 32. Можно ли сказать, что одна из них является более длинной, но более быстрой, чем другая? Почему да или почему нет? (При решении этой задачи нет необходимости точно подсчитывать количество байтов и циклов.) Статья 38 Двоичные умножение и деление Пусть у нас есть предложение Бейсика K=I>|<J. Как вы- полнить это действие на языке ассемблера? В некоторых про- цессорах имеются команды, подобные нашим ADC и SBC, но предназначенные для умножения и деления. На таком процес- 173 10101101 Х199 XI1000111 1557 10101101 1557 10101101 173 10101101 34427 00000000 00000000 00000000 10101101 10101101 1000011001111011 Рис. 38.1. соре умножение потребует трех команд: загрузки I, умножения на J и записи в К. В процессоре 6502, однако, не предусмотрены команды умножения и деления; вместо них нам приходится использовать подпрограммы. В отличие от подпрограмм
Двоичные умножение и деление 135 RDKEY и COUT, которые входят в состав монитора Эпл, под- программы умножения и деления нам придется создать самим— в мониторе их нет. Для того чтобы научиться писать и исполь- зовать такие подпрограммы, сначала мы должны познакомить- ся с тем, как умножаются и делятся двоичные числа. Мы уже видели в статье 3, что правила сложения и вычита- ния двоичных многоразрядных чисел совпадают с правилами в десятичной системе, если 10 заменить на 2. То же справедливо по отношению к умножению и делению. В качестве примера на 34427 173 1712 1357 1557 12^1 173 199 1000011001111011 10101101 10111111 10101101 100101110 10101101 100000011 10101101 10101101 10101101 о Рис. 38.2. 10101101 11000111 рис. 38.1 представлено умножение 173 на 199 в десятичной и двоичной системах. Двоичное умножение по сравнению с десятичным требует больше времени, однако с теоретической точки зрения проще. Например, в процессе десятичного умножения мы должны ум- ножить 173 на 9, чтобы получить 1557. Ничего подобного не встречается при двоичном умножении; мы всегда умножаем на 1 или 0, что очень просто. Перемножение двух 8-разрядных величин дает 16-разряднук> величину. Мы можем, например, написать программу перемно- жения содержимого регистров А и X, но результат не помес- тится в один регистр. В этом случае можно расположить ре- зультат в регистрах А и X вместе так, чтобы старшее полуслово хранилось в регистре А, а младшее — в регистре X. Может получиться так, что результат окажется 8-разрядной величиной. Тогда он попадает в регистр X, а в регистре А будет нуль. Для таких случаев можно написать более простую про- грамму умножения, которая предполагает, что результатом ум- ножения является 8-разрядная величина, и не работает в про- тивном случае. Рассмотрим теперь деление 34427 на 173 в десятичной и дво- ичной системах (рис. 38.2).
136 Статья 38 Двоичное деление, как и умножение, требует по сравнению с десятичным больше времени, но теоретически оно проще. В про- цессе десятичного деления мы должны умножить 173 на 9, как и при десятичном умножении. При двоичном делении мы всегда вычитаем одну и ту же величину, в нашем примере 10101101. Деление обратно умножению; поэтому берем 16-разрядную величину и делим на 8-разрядную. В программе делимое может размещаться в регистрах А и X вместе так, что в регистре А хранится старшее полуслово, а в регистре X — младшее. Дели- тель можно разместить в регистре Y. Результат деления может не поместиться в один 8-разрядный регистр (в частности, деление на 1 дает исходное 16-разрядное число). Однако принято считать, что результат содержит не более 8 разрядов; если частное оказывается длиннее, программа аварийно завершается (то же получится, если попытаться раз- делить на нуль — такая операция не определена). Для обнаружения переполнения и деления на нуль можно предложить простую и изящную проверку. Если мы делим 256*А-|-Х на Y (заметьте, что 16-разрядная величина со стар- шим полусловом в регистре А и младшим — в регистре X имеет значение 256>|<сА-|-Х), получая в результате Q^256 (переполне- ние), то 256*A+X>Q*Y>256*Y, или A+(X/256)>Y; по- скольку А и Y — целые числа и Х<256 (следовательно, Х/256< <1), получаем A^sY. Если же Y=0, то по-прежнему A^sY. Поэтому простая проверка условия A<Y достаточна для полу- чения уверенности в том, что не произойдет ни переполнения, ни деления на нуль. При делении вместе с 8-разрядным частным может образо- ваться 8-разрядный остаток. После завершения программы де- ления можно оставить частное в регистре X, а остаток в реги- стре А. Правила умножения и деления шестнадцатеричных чисел, такие же, как для двоичных чисел, но 2 заменяется на 16. Заин- тересованный читатель может решить несколько примеров с шестнадцатеричными числами, но мы заниматься этим не будем. Можно даже заметить, что простейший способ умножить или разделить шестнадцатеричные числа — это преобразовать их в двоичную форму, выполнить умножение или деление, и затем вновь преобразовать результат в шестнадцатеричную форму. Упражнения 1. Выполните предлагаемые примеры на умножение в дво- ичной форме, считая множители 4-разрядными числами: а) 9*9; if: б) 3* Ю (умножьте 3>|<10, а не 10>|<3); в) 15*15.
Подпрограмма умножения 137 форме: 2. Выполните предлагаемые примеры на деление в двоичной. * а) 1000110: 111; * б) 1000000 :1010; * в) 11001000:1101. 3. В упр. 2 выразите делимое, делитель, частное и остаток в десятичной форме. Статья 39 Подпрограмма умножения На рис. 39.1 приведена подпрограмма умножения двух 8-раз- рядных величин. Вы можете включить эту подпрограмму в свою программу и вызывать ее командой JSR MULT (так же как для ввода символа вы писали JSR COUT). Перед тем как вызвать MULT, вы должны загрузить величины, которые надо перемно- жить, в регистры А и X; подпрограмма MULT перемножает то, что она находит в этих регистрах и образует 16-разрядный ре- зультат, который остается в регистрах А и X (младшее полу- слово в регистре А, старшее — в регистре X). После выполнения строки JSR MULT вы можете, например, перенести содержимое регистров А и X в два байта 16-разрядной переменной. Конечно, если вы знаете заранее, что результат поместится в 8 разрядов, вы найдете его в регистре X, а в регистре А будет нуль. Пере- множаемые числа рассматриваются как числа без знака: (в статье 88 будет показано, как умножать числа со знаком). Для того чтобы, например, получить МЗ=М1>|«М2, где Ml, М2 и М3 — 8-разрядные величины без знака, можно написать LDA Ml ; Загрузим LDX М2 ; сомножители JSR MULT ; Теперь перемножим их STX М3 ; и отправим результат в М3 Если М3—16-разрядная величина с переставленными байтами.
138 Статья 39 следует еще добавить в конце программы STA МЗ+11, чтобы записать левое полуслово (команда STX М3 уже записала пра- вое полуслово). Мы видим, что при входе в подпрограмму MULT числа I и J должны находиться в регистрах А и X, а при выходе из MULT 16-разрядная величина !>|<J находится также в реги- ; Подпрограмма умножения: К=1>М (без знака) ; К имеет длину 16 разрядов, I и J по 8 разрядов Номе- ; При входе I и J находятся в регистрах А и X ра ; При выходе К находится в регистрах А и X строк ; (левое полуслово К в А, правое полуслово К в X) 1 MULT STX MDATA1 ; Записать I и J, при этом I 2 LSR ; уже сдвинуто вправо и 3 STA MDATA2 ; правый бит в разряде переноса 4 LDX #’8 ; Счетчик цикла 5 LDA *’0 ; Сумма начинается с 0 6 MULTI BCC MULT2 ; Этот бит=0? 7 CLC ; Нет, сбросить флажок переноса 8 ADC MDATA1 ; и прибавить J 9 MULT2 ROR ; Сдвинуть сумму (двойной сдвиг) 10 ROR MDATA2 ; Следующий бит в разряде переноса 11 DEX ; Повторить это 8 раз 12 BNE MULTI ; для 8 бит в I и J 13 LDX MDATA2 ; Правое полуслово результата 14 RTS ; сдвинулось в MDATA2. Загрузить 15 MDATA1 DFS II ; его и выйти 16 MDATA1 DFS !1 ; Сомножитель (I) ; Сомножитель (J) Рис, 39.1, Программа умножения. страх А и X. Это очень распространенный прием при работе с подпрограммами на языке ассемблера: если подпрограмма ра- ботает с некоторыми входными данными, эти данные обычно передаются в нее через регистры, и, если подпрограмма обра- зует некоторые выходные данные, эти данные обычно остаются в регистрах. Таким образом, вы можете вызывать подпрограмму многократно, загружая в регистр каждый раз разные величины и записывая результат каждый раз на новое место (так, при входе в подпрограмму COUT в регистре А должен находиться выводимый символ, а при выходе из подпрограммы RDKEY введенный символ оказывается в регистре А). Программа умножения и программа деления в следующей статье являются примерами того, насколько эффективные про- граммы можно написать на языке ассемблера. Это, конечно, не
Подпрограмма умножения 139 значит, что ваши собственные программы должны быть уже сейчас столь же эффективны. Каждая из приведенных программ представляет собой результат длительного процесса совершен,- ствования, гораздо более длительного, чем это можно допустить для каких бы то ни было подпрограмм, кроме самых общеупо- требительных и часто используемых. Объясним теперь, как работает MULT. В начале программы в строках, начинающихся с точки с запятой (см. статью 18}, да- ны общие комментарии. В них четко указано, для чего пред- назначена подпрограмма, как передаются данные при входе в подпрограмму и при выходе из нее. Обязательно снабжайте комментариями любую написанную вами подпрограмму. Как и в предыдущей статье, мы складываем здесь 8 чисел. Это делается в цикле, причем счетчиком цикла служит регистр X, куда первоначально загружается число 8 (строка 4). В конце каждого шага происходит декремент счетчика (строка 11) и проверка его на нуль (строка 12). Сложение выполняется в двойном регистре: старшее полу- слово находится в регистре А, а младшее — в ячейке MDATA2. Левые 7+й разрядов этого регистра используются, когда мы прибавляем k-e слагаемое (в регистре X в этот момент 9-й). Оставшаяся часть MDATA2 с одним промежуточным разрядом (в нем всегда 0) содержит величину, которая первоначально была в регистре А. Мы умножаем на эту величину, при этом последовательно проверяем ее биты справа налево. Каждый раз, когда бит имеет значение 1, мы прибавляем число, первоначаль- но находившееся в регистре X. Если бит имеет значение 0, при- бавлять не нужно. Это следует из соотношений Х>|<1=Х и х*о=о. Двойное назначение ячейки MDATA2 возможно потому, что обе величины сдвигаются вправо: по мере того как одна вдви- гается, другая выдвигается. В конце концов правая величина выдвигается из MDATA2 полностью и 16-разрядный регистр> (т. е. регистр А и ячейка MDATA2) содержит результат умно- жения. После этого мы загружаем регистр X из MDATA2 (стро- ка 13), и результат умножения оказывается в регистрах А и X, как и было определено с самого начала. Программа начинается с установки начальных значений MDATA1 (строка 1) и MDATA2, а также флажка переноса (строки 2 и 3). Заметьте, что, когда мы переходим на точку MULTI, флажок переноса уже содержит правый бит из реги- стра А; этот бит и проверяется в точке MULTI. В строке 5 при- сваивается начальное нулевое значение сумме, что позволяет потом прибавить к ней 8 величин. В точке MULTI мы проверяем очередной бит из регистра А.. Если он установлен в 1, мы прибавляем к сумме J, предвари-
wo Статья 39 тельно сбросив флажок переноса (строки 7 и 8). При этой опе- рации сложения перенос либо возникает, либо нет. В точке MULT2 мы сдвигаем частичную сумму вправо, чтобы к ней мож- но было добавить следующий член. Обычно такой сдвиг осуще- ствляется командами LSR и ROR (см. статью 34), однако мы заменили LSR второй командой ROR, чтобы вдвинуть признак переноса от сложения (если в точке MULTI осуществляется пе- реход, флажок переноса сброшен и вдвигается 0). Этот двойной сдвиг (строки 9 и 10) попутно переносит следующий бит из MDATA2 (считая справа налево) в разряд переноса, где он будет проверен в следующем шаге цикла (строка 6). Поскольку рассматриваемый текст является подпрограммой, он заканчивается командой возврата RTS (Return from subroutine — возврат из подпрограммы). Эта команда аналогич- на предложению RETURN в Бейсике или Фортране (в статье 60 будет показано, как эта команда в действительности выполня- ется). Заметьте, что непосредственно за командой RTS идут данные; в программах на языке ассемблера мы не будем ис- пользовать секцию данных совместно с подпрограммой Упражнения 1. Напишите предложения Бейсика, соответствующие приве- денным ниже последовательностям предложений языка ассем- блера (считайте, что подпрограмма MULT соответствует рис. 39.1): * а) LDA CLC Р ADC Q LDX DEX R JSR MULT STX S «) LDA #110 LDX N JSR ТХА MULT CLC ADC D STA N . эИ в ) LDX J -; • ' ТХА
Подпрограмма умножения 14>‘ JSR MULT ТХА JSR MULT STX К 2. Напишите программы на языке ассемблера, соответствую- щие приведенным ниже предложениям Бейсика (опустите ORG, END и DFS). Считайте, что массив Т начинается с Т(1) и что все переменные соответствуют 8-разрядным величинам: a) Q=R*S-1 * б) T(J)=K*(J“5) в) W=T(I^«J). 3. а) Сколько байтов памяти занимает программа на рис. 39.1? Для получения ответа сложите данные о длине ко- манд (взяв их из табл. П4) б и прибавьте байты, занимаемые данными. 4с б) Укажите минимальное и максимальное число машинных циклов, требуемых для выполнения программного цикла в под- программе MULT. Для получения ответа просуммируйте число машинных циклов в каждом из двух случаев (команда ВСС вы- полняет или не выполняет переход), умножьте на число шагов и вычтите 1, поскольку команда BNE при отсутствии перехода требует на один машинный цикл меньше. Используйте табл. П4. Не забудьте, что команда ADC выполняется, только когда ВСС не вызывает переход. в) Определите полное число машинных циклов (минималь- ное и максимальное), используемых подпрограммой MULT, при- бавив к полученному ранее результату число машинных циклов, требуемых командами вне программного цикла. *> Отыскивая данные в табл. П4, не пользуйтесь вариантами команд с адресом Z. Например, для мнемоники STX вы найдете вариант STX Q, который дан как- 3-байтовая команда (8Е cd ab) и вариант STX Z, который дан как 2-байтовая команда (86 ef). Не используйте STX Z или другие ко- манды с адресом Z из табл. П4, пока вы не дойдете до статьи 74, где будет рассказано, зачем нужны такие варианты команд. < '
142 Статья 40 Статья 40 Подпрограмма деления На рис. 40.1 приведена подпрограмма деления 16-разрядной величины на 8-разрядную величину; предполагается, что обе величины без знака. Чтобы использовать эту подпрограмму, прежде всего загрузите 8-разрядную величину в регистр Y, а 16-разрядную — в регистры А и X (старшее полуслово в регистр А и младшее—в регистр X). Если вы делите одну 8-разрядную величину на другую, загрузите первую 8-разрядную величину в регистр X и очистите регистр А, как обычно. Теперь вызывайте подпрограмму DIV (JSR DIV); она разделит первую величину на вторую и поместит результаты в регистры X и А: частное — в регистр X и остаток — в регистр А. После JSR DIV обычно сто- ит команда записи содержимого регистра X в память; содержи- мое регистра А игнорируется, потому что при делении нам чаще всего не нужен остаток. Однако остаток, который часто назы- вают MOD (I, J) (при делении I на J), находится в регистре А, если он вам зачем-то нужен. Для того чтобы, например, получить МЗ=М2/М1, где Ml, М2 и М3 — 8-разрядные величины без знака, можно написать LDA =Н=Ю ; Загрузим LDX Ml ; делимое LDY М2 ; и делитель JSR DIV ; Теперь разделим их STX М3 ; и отправим результат в М3 Если Ml — 16-разрядная величина с переставленными байтами, надо заменить LDA 4£!0 на LDA Ml-J-ll (LDX Ml остается без изменения). При выходе из этой программы деления установленный фла- жок переноса сигнализирует об ошибке. Ошибка возникает по двум причинам: либо была попытка разделить на нуль, либо результат деления не помещается в один байт. В обоих случаях при выходе из подпрограммы будет установлен флажок перено- са; в противном случае флажок переноса будет сброшен. По- этому сразу после JSR DIV можно использовать что-нибудь вроде BCS ERROR, чтобы провести проверку на ошибку. Ис- пользуя терминологию предыдущей статьи, можно сказать, что при входе в подпрограмму DIV делимое должно находиться в регистрах А и X, а делитель — в регистре Y; при выходе из под
Подпрограмма деления 143 ; Подпрограмма деления: K=I/J (без знака) ; I имеет длину 16 разрядов, J и К по 8 разрядов ; При входе J в регистре Y, I в регистрах А и X ; (левое полуслово в А, правое полуслово в X) ; При выходе К в регистре X, остаток в регистре А ; При выходе флажок переноса = 1 — нормальное Номе- ; завершение, ра ; флажок переноса =0 — ошибка строк ; (деление на 0 или ответ слишком велик) 1 DIV STX DDATA2 Правое полуслово делимого 2 STY DDATA1 Делитель 3 LDX #18 Счетчик цикла 4 СМР DDATA1 Ответ слишком велик, или деление на 0? BCS DIV4 Да, выход с установленным 5 переносом 6 DIV1 ROL DDATA2 Сдвинуть двойной регистр 7 ROL (и вдвинуть бит результата) 8 ВСС DIV2 Результат сдвига 17-разрядный? 9 SBC DDATA1 Да, всегда вычитание 10 SEC Но бит результата =1 11 BCS DIV3 и переход, безусловно, 12 DIV2 CMP DDATA1 Вычитание дает отрицательный 13 BCC DIV3 результат (бит результата =0)? 14 SBC DDATA1 Нет, вычесть (бит результата =1) 15 DIV3 DEX Если не закончили, повторить 16 BNE DIV1 с новым битом результ. в разр. переноса 17 ROL DDATA2 Вдвинуть последний бит результата 18 LDX DDATA2 и загрузить результат в X (перенос =0) 19 DIV4 RTS Выход (перенос =1 — ошибка) 20 DDATA1 DFS !1 Делитель 21 DDATA2 DFS 11 Правое полуслово делимого. Результат Рис. 40.1. Программа деления. программы частное находится в регистре X, а остаток — в ре- гистре А. Объясним теперь, как работает DIV. Как и раньше, в начале подпрограммы даны комментарии (они начинаются с точки с запятой), поясняющие, для чего предназначена подпрограмма я каковы условия входа и выхода из нее. Мы начинаем с записи
144 Статья 40 делителя (строка 2) и правого полуслова делимого „(строка 1)' и установки начального -значения счетчика цикла (строка 3), так же как и в программе MULT (цикл в этой программе, как и в MULT, заканчивается командами DEX и BNE на строках 15 и 16). Далее мы проверяем условие того, что старшее полу- слово нашей 16-р азрядной величины больше Y. Если это условие не выполняется, то имеет место одна из двух ошибочных ситуа- ций либо -Y-=0, либо результат будет слишком велик (см. статью 38). Мы переходим на возврат из подпрограммы (стро- ка 19) с установленным флажком переноса (поскольку переход выполняется командой BCS). В противном случае мы продол- жаем, и, как мы увидим в дальнейшем, флажок переноса в конце окажется сброшенным. • Вычитание выполняется в двойном регистре: старшее полу- слово— в регистре А и младшее — в ячейке DDATA2. Левые 17 — k разрядов этого регистра используются, когда мы выпол- няем ft-e вычитание для получения ft-го бита результата (в реги- стре X в этот момент 9—ft). Оставшаяся часть DDATA2 с одним промежуточным разрядом (в нем всегда 0) содержит результат, вдвигаемый в DDATA2 справа. Двойное назначение ячейки DDATA возможно потому, что оба числа сдвигаются влево. Когда мы первый раз подходим к точке DIV1, флажок пере- носа сброшен (иначе мы перешли бы на DIV4). Поэтому мы вдвигаем (командой циклического сдвига) промежуточный ну- левой бит. Двойной сдвиг осуществляется здесь в упомянутом выше двойном регистре. Если двойной сдвиг приводит к возникновению переноса, мы получаем 17-разрядное число, старшим битом которого служит признак переноса. В этом случаемы всегда вычитаем (строка 9), и поэтому следующий бит результата всегда равен 1. Поэтому мы переходим (строка 11) на конец цикла с установленным флажком переноса (строка 10). Заметьте, что команда BCS в этом случае, безусловно, выполняет переход. Если двойной сдвиг не приводит к возникновению переноса, нам следует выяснить, надо ли выполнять вычитание (строка 12). Если нет, мы переходим на конец цикла (строка 13) со сбро- шенным флажком переноса. Если да, это значит, что флажок переноса установлен (иначе был бы выполнен переход), и мы можем вычитать без команды SEC; само вычитание оставляет флажок переноса установленным (в противном случае результат был бы отрицательным). Важным обстоятельством является возможность перейти на метку DIV3 тремя способами (переходом со строки 11, перехо- дом со строки 13 и со строки 14); в любом случае следующий бит результата находится в разряде переноса.
Подпрограмма деления 145 Цикл заканчивается обычным образом. Если мы возвраща- г емся в точку DIV1, первая команда ROL вдвинет следующий бит результата в DDATA2 (таким образом, обычная пара команд < двойного сдвига ASL и ROL заменена здесь двумя командами ROL). Если мы не возвращаемся, последний бит результата вдвигается (строка 17) в DDATA2. Одновременно промежуточ- > ный нулевой бит выдвигается в разряд переноса (так что при нормальном завершении перенос сброшен); результат загружа- ется в регистр X (строка 18). Остаток, который есть не что иное,, как результат последнего вычитания, остается в регистре А. Упражнения 1. Напишите предложения Бейсика, соответствующие приве- денным ниже последовательностям предложений языка ассем- блера, считая, что MULT — это подпрограмма, приведенная на рис. 39.1, a DIV — подпрограмма, приведенная на рис. 40.1: ) * а) LDA #10 LDX Q LDY R DEX i INY JSR DIV STX S ' 6) LDA #1100 LDX #!0 LDY J 1 JSR DIV STA С * в) 1 LDA I SEC SBC J LDX #110 JSR MULT LDY К JSR DIV STX L 2. Напишите программы на языке ассемблера, соответствую- щие приведенным ниже предложениям Бейсика опустите (ORG, 10—589
146 Статья 41 END и DFS). Считайте,что все переменные соответствуют 8-раз- -рядным величинам: 5К a) I = (J+K)/L; б) D==MOD(N, 10)4-С; в) W=(L/M)/N. 3. Выполните упр. 3 предыдущей статьи (все три пункта) для «программы, приведенной на рис. 40.1. В п. б) надо рассмотреть три случая: 1) первая команда ВСС не выполняет переход; 2) первая команда ВСС выполняет переход, а вторая — нет; 3) обе команды выполняют переход. Удостоверьтесь в том, что ш каждом из этих трех случаев вы подсчитываете только дейст- вительно выполняемые команды. Статья 41 Входное и выходное преобразования Мы уже научились вводить символы (с помощью системной подпрограммы RDKEY), выводить символы (с помощью COUT), вводить строку символов (с помощью GETLNZ) и выводить строку (с помощью цикла, в котором вызывается подпрограмма COUT). В большинстве программ, однако (если исключить про- граммы, обрабатывающие только символьные строки), мы долж- ны также обращаться к подпрограммам преобразования целых чисел и других данных Каждое целое число имеет внутреннюю форму и внешнюю форму. Рассмотрим, например, число 209. Оно имеет следующие 4>ормы: „ , Внутреняя Внешняя форма Шестнадцатеричная В2 ВО В9 Двоичная 10110010 10110000 10111001 Символьный код «209» D1 11010001 «Q» *> В Бейсике такие преобразования тоже выполняются, но подпрограммы являются частью системы Бейсика и вызываются автоматически по мере на- добности. г J
Входное и выходное преобразования 14Т Внешняя форма — это форма ввода-вывода. Когда вы вво- дите с клавиатуры число 209, в ЭВМ поступают символьные коды символов 2, 0 и 9, т. е. числа В2, ВО и В9 (в шестнадца- теричном обозначении). Чтобы получить на экране число 209„ программа должна последовательно вывести эти три символьг ные кода. Внутренняя форма используется при вычислениях, например, при сложении или вычитании. В нашем случае двоичной внут- ренней формой будет 11010001, потому что двоичным выраже- нием десятичного числа 209 является 11010001. В шестнадцате- ричной форме оно превращается в D1. Это число случайно сов- падает с символьным кодом буквы Q, что для нас не имеет ни- какого значения. Если мы хотим, например, написать программу, которая поз- волит ввести с клавиатуры "209+170" и затем выведет на эк- ран "379" (сумму 209 и 170), то в ней должны быть предусмот- рены следующие шаги: 1) ввод символов "209" и "170" (во внешней форме); 2) преобразование их из внешней во внутреннюю форму (это- называется входным преобразованием); 3) сложение их во внутренней форме; 4) преобразование результата из внутренней во внешнюю- форму (это называется выходным преобразованием); 5) вывод результата (т. е. "379"). Преобразование во внутреннюю форму часто называют пре- образованием в двоичную форму, а сама внутренняя форма ши- роко известна под названием двоичной формы. Это, строго го- воря, неправильно; все числа в ЭВМ являются двоичными. Од- нако внутренняя форма, записанная в двоичной системе счи- сления, действительно является двоичным представлением десятичного числа, как уже говорилось в статье 2. В статьях 62 и 63 будут описаны две программы преобразо- вания, DECI (Decimal input — десятичный ввод) и DECO (De- cimal output — десятичный вывод). В данный момент мы огра- ничимся только обсуждением вопроса, как работать с програм- мами DECI и DECO. Это обстоятельство объясняется тем, что они содержат команды, которые изучаются позже *>. При входе в программу DECI в регистре X должен содер- жаться индекс символа в стандартном буфере ввода. Если, на- пример, мы, как обычно, определили INBUF с помощью пред- ложения EQU $0200, то команда LDA INBUF, X загрузит символ с номером X из входной строки. Таким образом, про- *> Не пытайтесь использовать эти команды (в особенности РНА и PLA) в ваших программах, пока не изучите их детально; их надо использовать с осторожностью. 10*
148 Статья 41 грамму DECI можно использовать для преобразования несколь- ких чисел из одной входной строки; если, однако, мы считыва- ем число, начинающееся с начала строки, мы должны при вхо- де в программу DECI очистить регистр X. При выходе из программы DECI преобразованное десятич- ное число находится в регистрах А и X (старшее полуслово в ; Эта программа вводит с клавиатуры число К и ; выводит на экран 2К XjETLNZ EQU ORG $FD67 $0800 ; Адрес программы GETLNZ ; Начало программной секции 4>ROG1 JSR GETLNZ ; Ввести число LDX #10 ; Входное преобразование при Х=0 JSR DECI ; (число начинается с начала строки) BCS PROG1 ; Если слишком велико, ввести новое STA IUPPER ; Преобразование заканчивается ; с числами STX ILOWER ; в регистрах А и X — записать их ASL ILOWER ; Двойной сдвиг ILOWER и ; IUPPER ROL IUPPER ; (умножение на 2) LDX ILOWER ; При вызове выходного преобразования LDA IUPPER ; преобразуемые числа в ; регистрах А и X JSR BRK DECOZ ; Вызов подпрограммы DECOZ ; Завершить программу Далее следуют либо тексты подпрограмм DECI и DECO, либо предложения -£QU, описывающие их адреса ORG $0900 ; Начало секции данных iLOWER DFS !1 ; Младшее полуслово 16-разрядного ; числа I IUPPER DFS END !1 ; Старшее полуслово I Рис. 41.1. Использование входного и выходного преобразований. А, младшее — в X); флажок переноса сбрасывается, если толь- ко данное число не оказывается слишком большим (больше 65535). В этом случае флажок переноса будет установлен (а содержимое регистров А и X лишено смысла). При входе в программу DECO регистры А и X должны со- держать 16-разрядное число, как при выходе из программы DECI. Это число преобразуется в десятичную форму и выводит- ся на экран с помощью программы COUT. На рис. 41.1 дан при- мер использования программ DECI и DECO. Заметьте, что в
Входное и выходное преобразования 149 программе на рис. 41.1 используется не DECO, a DECOZ; про- грамма DECOZ начинает вывод с новой строки (программа DECO продолжает заполнять текущую строку). Имеются два способа передачи системе LISA информации о местонахождении подпрограмм. Можно включить все коман- ды подпрограмм в состав вызывающей их программы и произ- вести ассемблирование всех этих программ вместе. Можно, од- нако, ассемблировать их порознь; например, подпрограмма DECI может быть ассемблирована с адреса $0900 (путем ис- пользования псевдокоманды ORG $0900 в качестве первого пред- ложения DECI), а подпрограмма DECO — с адреса $0А00. В этом случае в состав вызывающей программы следует вклю- чить предложения DECI EQU $0900 и DECO EQU $0А00, чтобы передать информацию об адресах подпрограмм *>. Если вы используете этот способ, проследите за тем, чтобы подпрограм- мы не были расположены слишком близко друг к другу и не перекрывались. Вместе с подпрограммами перекодировки можно использо- вать подпрограмму PRBL2 (PRBL обозначает print blanks — напечатать пробелы), которая выводит п пробелов. При входе в подпрограмму число пробелов должно находиться в регистре X. Так, чтобы вывести 10 пробелов, следует использовать команды LDX #!10 и JSR PRBL2, при этом адрес этой подпро- граммы указывается предложением PRBL2 EQU F94A. Подпрограммы DECI и DECO (и DECOZ) используют в про- цессе выполнения регистры, так же как подпрограммы RDKEY и GETLNZ (см. статью 25). Поэтому после вызова командой JSR одной из этих подпрограмм, не надейтесь, что регистр Y, например, будет содержать то же значение, что и перед JSR. Упражнения 1. Что дает выполнение каждой из приведенных ниже про- грамм, если вводятся числа 50, 100 и 200 (подпрограммы DECI и DECOZ описаны в настоящей статье, DIV — в статье 40, а GETLNZ — в статье 25) ? * a) JSR GETLNZ LDX #!0 JSR DECI INX О Из рис. 63.1 видно, что DECO начинается с 8-го байта этой програм- мы, так как команды PHA, JSR и JMP в сумме занимают 7 байт (см. табл. П4). Поэтому если DECOZ при ассемблировании получила адрес ?0А00, то DECO будет иметь адрес $0А07, и в любую программу, содер- жащую вызов JSR DECO, следует включить предложение DECO EQU Г0А07.
150 Статья 41 BNE NEXT CLC ADC #11 NEXT JSR DECOZ 6) JSR GETLNZ LDX #10 JSR DECI CMP #10 BNE NEXT CPX #!100 BNE NEXT TAX NEXT JSR DECOZ * B) JSR GETLNZ LDX #10 JSR DECI LDY #no JSR DIV LDA #10 JSR DECOZ 2. Напишите программы, выполняющие описанные ниже дей- ствия (с помощью DECI, DECOZ, DIV и GETLNZ): а) введите с клавиатуры одно число, запишите его в ячей- ки READ и READ-J-H (с переставленными байтами) и выведи- те его на экран; б) выведите на экран произведение 8-разрядной величины J на 200 (используйте подпрограмму MULT); в) введите строку с двумя числами (первое начинается с позиции 1, а второе — с позиции 7) и выполните переход на мет- ку NOTEQ, если они не равны (числа могут иметь значения от 0 до 65 535). 3. Какая ошибка содержится в следующей программе-выво- да чисел от 1 до 10? LDX #!0 STX J LOOP INC J LDX J СРХ #111 BEQ DONE
Законченные программы 151 JSR DECOZ JMP LOOP DONE (следующая команда) Статья 42 Законченные программы Теперь мы готовы к тому, чтобы написать законченную про- грамму. В последующих восьми статьях мы научимся ассембли- ровать, отлаживать и запускать программы на ЭВМ Эпл. В процессоре 6502 в отличие от многих других процессоров не предусмотрена команда остановки ЭВМ. Основная програм- ма, выполняемая на ЭВМ Эпл, обычно заканчивается командой BRK (Break —внутреннее прерывание), которая передает уп- равление программе, называемой монитором (о ней будет идти речь в статье 47) *>. Приведем пример законченной программы. Она вводит с клавиатуры текст, заканчивающийся символом control-E. При ее завершении (с помощью команды BRK) число, содержащее- ся в регистре А, показывает, сколько раз во введенном тексте Встречалось отдельное слово AGE (возраст). RDKEY EQU $FD0C ; RDKEY подпрограмма Эпл CTRLE EQU $85 ; Символ control-E ORG $0800 ; Начало программной секции LDA #!0 ; Начальное значение счетчика STA COUNT 5 (количество слов AGE)=0 JMP L3 ; Первую проверку сначала пропустить LI JSR RDR ; Ввести один символ L2 JSR CAC ; Проверить, является ли он буквой BCC LI ; Если нет, проверить дальнейший текст D Вы можете также закончить основную программу командой JMP INIT (если в программе было определение IN IT EQU $FB2F). Эта команда так- же передает управление монитору, но более наглядным способом.
152 Статья 42 LDX #!3 ; на AGE (в AGE 3 символа) L3 JSR RDR S Ввести один символ CMP AGE—!I,X ; Проверить следующий символ AGE BNE L2 ; Если не равно, проверить на букву DEX > Если равно, проверить, есть ли еще BNE L3 символы в "AGE", если да, повторить JSR RDR ; Ввести еще один символ JSR CAC 5 является ли он буквой? BCC LI Да, вернуться (не AGE) INC COUNT ; Нет, инкремент сметчика COUNT JMP L3 Ввести следующий символ RDR STX TEMP ; Подпрограмма ввода символа — сохранить X,. JSR RDKEY ; получить символ, снова восстановить X, LDX TEMP ; поскольку RDKEY сама использует регистр X CMP #CTRLE ; Если не равно control-E BNE DONE ; выйти из подпрограммы LDA COUNT control-E — загрузить COUNT в BRK регистр А и прерывание CAC CMP #"A" Подпрограмма проверки на букву — BCC CAC1 если меньше А CMP #"Z"+!1 ; или больше Z, установить разряд переноса RTS > (подпрограмма возвращает » установленный разряд CAC1 SEC переноса, если символ оказался буквой, DONE RTS » и сброшенный разряд переноса в противном случае) ORG $ 0900 > Начало секции данных AGE ASC EGA ; Строка AGE задом наперед TEMP DFS и ; Временная ячейка для X COUNT DFS и Число слов AGE END Конец программы1) О Приведенная программа, очевидно, ошибочна. Если по команде JMP L3 в начале программы перейти на метку L3 (обойдя строку LDX #13), то в команде СМР AGE — !1, X индексная адресация будет использована пр» неопределенном значении индекса в регистре X. Более правдоподобный ва- риант начального участка программы выглядит следующим образом (ком- ментарии к строкам не изменяются): RDKEY EQU $FD0C CTRLE EQU $85
Законченные программы 153 Эта программа является интерактивной: вы вводите анали- зируемый текст в процессе выполнения программы. В отличие от такого режима системы с дистанционным вводом заданий (обыч- но на больших ЭВМ) требуют от вас заполнения входного фай- ла анализируемыми данными до запуска программы; в процес- се выполнения программа считывает данные из входного файла. Если после запуска рассмотренной программы ввести текст THE AGENT OF THE MANAGER SAID THAT MY TEEN- AGE SON WAS UNDER AGE, BUT MY DAUGHTER WAS OF SUFFICIENT AGE TO MANAGE THE STORE и вслед за ним control-E, то в регистре А окажется число 3, так как будет подсчитано количество слов AGE в сочетаниях TEEN- AGE, UNDER AGE и SUFFICIENT AGE (но не буквосочетания AGE, входящие в состав слов AGENT, MANAGER или MANAGE). ORG $0800 LDA #ю STA COUNT JMP L4 LI JSR RDR L2 JSR САС ВСС L1 L4 LDX #13 L3 JSR RDR СМР AGE—!1,Х BNE L2 DEX BNE L3 JSR RDR JSR САС ВСС L1 INC COUNT JMP L4 (продолжение программы) •— Прим, перев. Помощник управляющего сказал, что мой сын-подросток слишком мо- лод, ио моя дочь достаточно взрослая, чтобы работать в магазине.—» Прим, перев.
154 Статья 42 Если вы пишете законченную программу, проверьте, выпол- нены ли следующие условия: 1) даны все необходимые определяющие предложения EQU (в приведенной программе с помощью предложения EQU долж- ны быть определены RDKEY и CTRLE); 2) в программе имеются программная секция, начинающая- ся с ORG, и секция данных, также начинающаяся с ORG (одна- ко см. далее, статья 45); 3) если у вас есть подпрограммы (как, например, RDR и САС в приведенной программе), они заканчиваются командой RTS (заметьте, что подпрограмма САС заканчивается разветв- лением с двумя отдельными командами RTS, стоящими в кон- це ветвей). Если у вас есть основная программа, она заканчива- ется командой BRK (заметьте, что в нашем примере BRK нахо- дится в середине подпрограммы RDR, и тем не менее эта коман- да характеризует конец основной программы); 4) объявлены все переменные. Это значит, что любая пере- менная, используемая в программе, должна иметь в секции дан- ных объявление типа J DFS п или J BYT и, или J ASC сообще- ние, или что-нибудь подобное (см. также статьи 63 и 73). В при- веденном примере так объявлены AGE, TEMP и COUNT, по- скольку эти обозначения используются в программной секции; 5) подпрограммы, если это необходимо, сохраняют и восста- навливают регистры. В приведенном примере цикл, начинаю- щийся' с метки L3, использует регистр X, но в нем также содер- жится вызов подпрограммы RDR, которая в свою очередь ис- пользует регистр X в процессе выполнения (см. статью 25). Поэтому в начале подпрограммы RDR текущее содержимое ре- гистра X сохраняется в ячейке TEMP, а после возврата из RDKEY — восстанавливается. В результате содержимое реги- стра как до, так и после вызова RDKEY одно и то же. Это не- обходимо для правильного выполнения цикла. В статье 57 будут даны более общие соображения относительно сохранения и вос- становления; 6) имеется предложение END, являющееся последним пред- ложением программы (в программе должно быть только одно предложение END). Упражнения 1. Приведите три причины, по которым приведенная ниже программа не является законченной: ORG $0800 DUMB LDA J SEC
Ручное ассемблирование 155 SBC I JSR COUT ORG $ 0900 I DFS 11 I DFS II 2. Приведите три причины, по которым приведенная ниже программа не является законченной: ORG $0800 BAD LDX I JSR WORSE STX I BRK END WORSE INX END 3. Дополните приведенную ниже программу, сделав ее за- конченной (секцию данных начните с адреса 0В00): ORG $0А00 KFIRST JSR GETLNS LDA INBUF STA К Статья 43 Ручное ассемблирование0 Отладка программ, написанных на языке ассемблера, требу- ет детального понимания соответствия между языком ассембле- ра и машинным языком. Лучший способ добиться понимания — Читатель извинит нас за использование этого несколько неуклюжего профессионального выражения, являющегося, впрочем, точным переводом оригинала (hand assembly). Этот жаргон использован и в следующей ста- тье.— Прим, перев.
156 Статья 43 выполнить ручное ассемблирование нескольких программ, дру- гими словами, провести их трансляцию с языка ассемблера на машинный язык вручную. В статьях 10 и 27 мы уже познакомились со всеми (кроме одного) форматами команд процессора 6502. К ним относятся: 1) однобайтовые команды (только код операции), например, INX; 2) команды с непосредственной адресацией, выполняющие действия над константами (например, LDA Ф $64). Второй байт в этом случае содержит константу (первый байт любой команды — всегда код операции); 3) команды, содержащие 16-разрядный адрес, например, STX К — во втором и третьем байтах хранится адрес (записан- ный в форме с переставленными байтами); 4) условные переходы, например, BNE L7 — второй байт представляет собой относительный адрес со знаком (который после прибавления к нему 2 и адреса команды условного пере- хода дает адрес перехода); 5) команды, содержащие 8-разрядный адрес, например, LDA $33 — во втором байте 8-разрядный адрес (как раз с этим ти- пом команд мы познакомимся позже, в статье 74). Все эти форматы, кроме первого и третьего, имеют длину 2 байт. Коды операций для всех этих случаев даны в табл. П4 (заметьте, что в табл. П4 для формата 3 используется буква Q, а для формата 5 — буква Z). Теперь рассмотрим некоторую программу в качестве образ- ца (рис. 43.1,а). В этой программе нет отдельных секций для программных строк и данных; этот вопрос будет рассмотрен дальше, в статье 45. Покажем, как выполняется ручное ассемб- лирование. Процесс состоит из следующих этапов: 1. Нарисуйте строки линий, как на рис. 43.1,6. Левый стол- бец будет в дальнейшем заполнен адресами, остальные столб- цы— данными (как на рис. 43.1, д). Все числа в столбцах запи- сываются в шестнадцатеричной форме. На этом этапе важно оставить нужное количество пробелов (в дальнейшем они будут заполняться числами) для каждой команды и для каждого эле- мента данных (один пробел на каждый байт). Заметьте, что на- до предусмотреть: у 1 * а) два пробела для команды LDA " (в ней использует- ся константа); б) три пробела для команды LDX W (W имеет 16-разрядный адрес); в) три пробела для команды СМР Т—11, X (Т имеет 16-раз- рядный адрес);
ORG $0900 т DFS 1100 W BYT 1100 ** S1 LDA — _ • •• LDX w — S2 СМР T—!1,X «• * BNE S3 * _ — DEX — « n BNE S2 w — S3 (следующая команда) - - — - a 6 0900 0900 0964 0964* 64 0965 0965 A9 AO 0967 0967 АЁ 096А 096A DD 096D 096D DO < 096F 096F CA 0970* 0970 DO 0972” 0972 в a ORG $0900 Т DFS 1100 0900 W BYT 1100 0964 64 S1 LDA 0965 AST AO LDX w 0967 AE~ 64 09 S2 CMP T—!1,X 096A dTF FF 08 BNE S3 096D DO- 03 DEX 096F CA" BNE S2 0970 DO F8 S3 (следующая команда) 0972 д Рис. 43.1. Ручное ассемблирование программы.
158 Статья 43 г) по два пробела для каждой команды BNE (условный пе- реход) ; д) один пробел для команды DEX (только для кода опера- ции); е) один пробел для псевдокоманды W BYT 1100 (W занима- ет 1 байт); ж) и ни одного пробела для Т (поскольку мы не знаем, что содержится в Т). 2. Теперь заполните первый столбец, как на рис. 43.1, в. За- метьте, что: а) первое число определено предложением ORG; б) каждое число плюс количество пробелов справа от него дает следующее число (так, 0965+2 дает 0967, потому что спра- ва от 0965 два пробела). Заметьте, что для псевдокоманды DFS мы должны прибавить число объявленных байтов (в дан- ном случае десятичное 100, или шестнадцатеричное 64) О. 3. Далее заполните все пробелы кроме тех, которые соответ- ствуют адресам, как на рис. 43.1, г. Заполняемые пробелы будут включать в себя: а) коды операций, полученные из табл. П4. Не забывайте при поиске кодов операций обращать внимание на способ адре- сации; например, команда СМР Т— II,X имеет код операции DD Ха не CD), потому что адрес определяется с помощью индексно- го регистра X (если бы в качестве индексного использовался регистр Y, код операции был бы D9). Не используйте режим адресации Z (например, СМР Z,X с кодом операции D5 —возь- мите вместо этого СМР Q, X), пока в статье 74 мы не изучим адресацию через нулевую страницу; б) константы (используемые в режиме непосредственной ад- ресации), например, АО (символьный код знака "", т. е. пробе- ла, получен из табл. ШО); в) данные, определенные с помощью объявления констант, например, объявления W BYT 1100, включающего десятичное 100 (шестнадцатеричное 64). 4. Наконец, заполните адреса, как на рис. 43.1,5. Вам при- дется ссылаться на адреса меток, которые уже определены в первом столбце. Например, адрес S1 равен 0965, потому что предложение с меткой S1 соответствует предложению машин- ного языка с адресом 0965. Адреса включают в себя: а) стандартные 16-разрядные адреса, например адрес W. Не забывайте переставлять байты; так, адрес W записывается как 64 09, а не 09 64; При использовании ассемблера LISA 1.5 в листинге первого столба может появиться ошибка. Она характерна только для листинга и не влияет на правильность машинной программы. В ассемблере LISA 2.5 эта ошибка исправлена.
Ручное ассемблирование 15&1 б) относительные адреса, вычисляемые по правилам, изло- женным в статье 27. Первый относительный адрес z в нашей программе определяется из выражения 096D+2-|-z=0972, от- куда z=3. Второй относительный адрес z определяется из вы- ражения 0970+2-|-z=096A, откуда z= —8, или F8 в форме до- полнения до двух; в) стандартные 8-разрядные адреса (см. статью 74); г) адреса со смещениями. В нашем примере адрес Т равен' 0900, поэтому Т—!1 составляет 0900—1 (шестнадцатеричное), т. е. 08FF. Переставив, как всегда, байты, получим FF08. Этим завершается процесс ручного ассемблирования. Вам следует проделать эти операции несколько раз, чтобы убедиться, что вы хорошо усвоили материал. Конечно, результаты ручного ассемблирования всегда можно проверить, выполнив ассембли- рование на ЭВМ, как это будет показано в статье 46. Упражнения >}с 1. Найдите три ошибки в приведенном ниже результате вы- полнения первого этапа ручного ассемблирования (как на рис. 43.1,6): ORG $ 0800 Т DFS 1100 N DFS 11 START LDA #" " LDX N LOOP STA T— !1, X DEX BNE LOOP 2. Найдите две ошибки в приведенном ниже результате вы- полнения третьего этапа ручного ассемблирования (как на рис. 43.1, г; первые два этапа здесь были выполнены без оши- бок): ORG $ 0800 T DFS 1100 0800 START LDX LOOP TXA #!16 0864 A2 16 0866 8A STA DEX T,X 0867 8D 086A CA BNE LOOP 086A DO
160 Статья 44 sf; 3. Найдите две ошибки в приведенном ниже результате вы- полнения четвертого этапа ручного ассемблирования (как на рис. 43.1, <9; первые три этапа здесь были выполнены без оши- 1 €юк): ; ORG Т DFS $0900 18 0900 W DFS START LDA !1 W 0908 f 0909 AD 09 08 LDX #!8 090C A2 08 j LOOP CMP T,X 090E DD 00 09 BEQ DONE 09H_ F0 05 DEX BNE LOOP 0913 CA 0914 DO F8 DONE (следующая команда) 0916 i Статья 44 Ручные проверка и прогонка Отладку любой программы, и особенно программы на языке ассемблера, никогда не следует начинать с прогона программы на машине. Сначала надо провести ручную проверку (desk chec- king), которая есть не что иное, как тщательная проверка за письменным столом того, что вы написали. Мы все делаем ошибки при программировании. Даже спе- циалисты по вычислительной технике с двадцатилетним опытом 4
Ручные проверка и прогонка 161 работы допускают их десятками. Разница между хорошим и плохим программистом заключается не в том, что первый нё де- лает ошибок, а скорее в том, что он делает меньше простых ошибок. Мы уже отмечали, что программа на языке ассемблера, должна содержать комментарии на каждой строке. Весьма по- лезно сначала написать программу без всяких комментариев (либо с ограниченным числом комментариев в ключевых мес- тах программы), а затем пройтись по программе, еще раз сос- тавляя комментарий к каждой команде. Старайтесь, чтобы ком- ментарии были максимально понятными. В ряде случаев это по- может вам выявить ошибки, которые могли бы остаться неза- меченными при более туманных комментариях Некоторые ошибки являются систематическими-, они возни- кают всякий раз при выполнении программы. Другие ошибки носят случайный характер; при каждом выполнении программы они будут приводить к разным результатам, либо программа может выполняться в большинстве случаев правильно, но время от времени неожиданно давать неверный результат. Такие слу- чайные ошибки следует стараться выявить на этапе ручной про- верки, потому что при машинном выполнении программы они могут исчезнуть лишь для того, чтобы снова появиться позже. Одна из наиболее типичных случайных ошибок возникает, если программист забыл инициализировать переменную, т. е. присвоить ей начальное значение. В этом случае начальное зна- чение переменной зависит от того, какая программа (назовем ее Р) выполнялась на ЭВМ перед тем, как была загружена от- лаживаемая программа. Если ваша переменная имеет адрес а, то программа Р может оставить после своего выполнения в ячей- ке по адресу а что угодно — код команды, данное, либо что-то, что сохранилось от выполнения еще более ранней программы — любой лусор (garbage). Полезно вести список допущенных вами ошибок. Каждый раз, когда вы обнаруживаете ошибку, заносите ее в этот спи- сок и обязательно проверяйте, не повторили ли вы ее в следую- щей программе. Особенно тщательно проверяйте инициализацию всех переменных. После окончания ручной проверки проведите несколько раз ручную прогонку (walkthrough) отдельных частей вашей про- граммы. Ручная прогонка — это процесс, который лучше всего *> Любопытно отметить, что именно так была найдена ошибка в про- грамме статьи 42. При переводе комментариев обнаружилось, что один из комментариев не точно соответствует'описываемой им команде. Тщательный анализ программы показал, что комментарий, характеризующий желаемое действие, правилен, но выполнение команды не приведет к желаемому ре- зультату. — Прим, перев. 11—589
162 Статья 44 проиллюстрировать на примере. На рис. 44.1 показана типич- ная процедура ручной прогонки; последовательные шаги про- гонки, обозначенные теми же буквами, что и на рис. 44.1, опи- саны ниже. (1) LDA #'0 (2) LDX *’1 (3) CLC (4) LOOP ADC Т-’ 1,Х (5) INX (6) СРХ N (7) BNE LOOP (8) STA SUM б) N А ' 4 0 в} N А в’ 4 0 X 1 г) N А г> 4 0 X 1 С 0 □ч N А 4 4У X 1 с 0 Т( 1) ч N А X с 4 £> 0 Т(1) 2 N А X с 4 J& X 0 ж') ТШ 2 Т(1)+ Т(2) N А X с 4 » Г 0 з) т*+г X Т(1)+ 3 Т(2) N А X с 4 в X 0 г 3 Т(1)+Т(2)+Т(3)
Ручные проверка и прогонка 163 N А X 4 0 X -WT Z к) -Т++г* -тет 4 Т( 1)+ Т(2)+Т(3) С О N А 4 Я X С 0 SUM Т(1) гГ\ W ^Т(2) ✓/; +Т(3) ^ff2^ 4 Т(1)-Т(2)+Т(3) Рис. 44.1. Ручная прогонка. а) Приведенная программа находит сумму нескольких чи- сел. Она уже была проверена на правильность инициализации переменных. В регистры А, X и разряд переноса исходные зна- чения заносятся в строках 1, 2 и 3; элементы массива Т, так же как и переменная N, была инициализированы в предыдущих строках программы. б) Для данной прогонки примем N=4 (такой выбор часто необходим; в противном случае прогонка займет слишком мно- го времени). Начнем составлять таблицу переменных. Посколь- ку в первой строке программы регистр А загружается нулем, мы можем написать, что N=4 и А=0. в) Выполняем строку 2, загружая 1 в регистр X (очевидно, что нам не надо заводить новую таблицу; мы просто добавляем к уже начатой таблице переменную X и ее значение 1). г) Выполняем строку 3 и сбрасываем флажок переноса (дру- гими словами, устанавливаем его равным нулю). д) Выполняем строку 4 и заносим в регистр А значение Ю+Т(Х), или просто Т(Х). Теперь посмотрим на столбец для X в таблице; Х=1, поэтому Т(Х) есть Т(1) (часто именно в этом месте обнаруживается ошибка; в нашей программе, одна- ко, здесь все верно). Если переменная принимает новое значение, записывайте новое значение и вычеркивайте старое. е) Выполняем строку 5 и прибавляем 1 к X. Теперь Х=2. ж) Выполняем строки 6 и 7. X равно N? 2 равно 4? Нет, поэтому мы возвращаемся и снова выполняем строку 4. Зано- сим в регистр А значение Т(1)-|-Т(Х), но Х=2, так что зано- сится Т(1)4-Т(2). (Именно здесь обнаружилась бы ошибка, если в программе была пропущена команда INX, или вместо INX на- писано DEX.) з) Выполняем строку 5 и прибавляем 1 к X. Теперь Х=3. и) Выполняем строки 6 и 7. X равно N? 3 равно 4? Нет, 11*
164 Статья 44 поэтому снова выполняем строку 4. В регистр А заносится зна- чение Т(1)+Т(2)4-Т(Х), но Х=3, поэтому в регистре А будет Т(1)+Т(2)+Т(3). (Если бы мы не выбрали N=4 для данной прогонки, на этом этапе стоило бы остановиться и начать про- гонку заново, иначе процесс прогонки сделался бы уж слишком утомительным.) к) Выполняем строку 5 и прибавляем 1 к X. Теперь Х=4. л) Выполняем строки 6 и 7. X равно N? 4 равно 4? Да, поэтому выполняем строку 8. Запоминаем сумму из регистра А в ячейке SUM, так что SUM=T(1)+T(2)+T(3). Получили ли мы то, что хотели? Возможно, нет (поскольку в сумме отсутствует Т(4)); таким образом, мы обнаружили ошибку. Ручную прогонку полезно проводить вдвоем с приятелем. Вы- полняйте, как было описано, строку за строкой и старайтесь независимо друг от друга обнаружить ошибки. Избегайте такой методики, однако, если выяснится, что вы испытываете искуше- ние переложить на своего приятеля (или он на вас) заботу о написании программы. После того как вы закончите ручную проверку и прогонку, займитесь чем-нибудь другим; позже, перед тем как сесть за ЭВМ, повторите их. Вы не поверите, сколько ошибок можно обнаружить таким способом. Упражнения 1. В приведенной ниже программе должно определяться (и заноситься в регистр Y) количество хвостовых пробелов в стро- ке INBUF, т. е. количество последовательных пробелов перед первым возвратом каретки ($8D). Проведите ручную прогонку программы, считая, что INBUF содержит "2", затем 2 пробела и возврат каретки. Выполняйте программу до тех пор, пока не станет ясно, в чем состоит ошибка Опишите ошибку и приве- дите таблицу (аналогичную таблице на рис. 44,1, л), доведен- ную до этого шага: ORG $0800 CRET EQU $8D INBUF EQU $0200 START LDA #CRET LDX #$FF LOOP1 INX CMP INBUF, X Возможно, вам удасться найти ошибку и без прогона программы, прос- то с помощью ручной проверки; выполните, однако, эти упражнения имен- но так, как сказано.
Ручные проверка и прогонка 165 BNE LOOP1 DEX LDY #10 LDA LOOP2 CMP INBUF, X BNE DONE INY JMP LOOP2 DONE (следующая команда) 2. В приведенной ниже программе в переменную SUM долж- на заноситься сумма всех положительных элементов массива чисел со знаком Т(1), Т(2), ... T(N) (отрицательные элементы игнорируются). Проведите ручную прогонку программы, приняв N=4, Т(1)=30, Т(2)= —5, Т(3) = —40 и Т(4) = 15. Как и в п. 1, выполняйте программу до тех пор, пока не станет ясно, в чем состоит ошибка, опишите ее и приведите таблицу. Считай- те, что массив Т начинается с Т(1): ORG $0850 Т DFS 1100 SUM DFS 11 START LDA #10 STA SUM LOOP LDX N LDAT-11, X BMI CONT CLC ADC SUM STA SUM CONT DEX BNE LOOP 3. В приведенной ниже программе в переменную MINALL должно заноситься минимальное число из чисел без знака Т(1), Т(2), ..., T(N). Проведите ручную прогонку программы до кон- ца, приняв N=2, Т(1)=3 и Т(2)=5, и приведите окончатель- ный вид таблицы. Есть ли в программе ошибка? Если есть, в чем она состоит? Считайте, что массив Т начинается с Т(1): ORG $08А0 Т DFS 1200 MINALL DFS 11
166 Статья 45 N DFS !1 START LDA #!0 STA MINALL LDX N LOOP LDAT-ll, X CMP MINALL BCS CONT STA MINALL CONT DEX BNE LOOP Статья 45 Ошибки смешивания, наложения и стартового адреса Строго говоря, мы можем написать программу на языке ас- семблера Эпл, не отделяя программную секцию от данных. В этом случае команды и данные будут объединены в одну сек- цию, и в программе будет только одно предложение ORG— в самом начале (можно даже и совсем опустить ORG — в этом случае принимается по умолчанию ORG $0800). Такое сме- шивание, однако, не должно приводить к тому, чтобы команд- ные строки незаметно переходили в строки данных. Чтобы по- казать, что это значит, рассмотрим такой фрагмент программы: 0807 А9 4С LDA #$4С 0809 8D ОС 08 STA L 080С L DFS !1 Такая последовательность строк неверна и недопустима. По- смотрим, как она будет выполняться. После того как выполнена команда 0809, процессор попытается выполнить команду по следующему адресу 080С. Следовательно, байт с адресом 080С будет трактоваться как код операции, хотя в действительности
Ошибки смешивания, наложения и стартового адреса 167 это совсем не код операции, а значение L. (Мы только что за- несли в этот байт с помощью команды STA число $4С, и, если рассматривать $4С как код операции, это обозначает переход по адресу, указанному в двух следующих байтах; к чему приве- дет этот переход, невозможно предугадать.) В каждый момент времени вы должны знать, что будет де- лать процессор в следующий момент. Если «ничего» — програм- ма в этой точке завершилась — значит, следующей командой должна быть BRK (в случае основной программы) или RTS (в случае подпрограммы). Вслед за командами BRK или RTS, естественно, могут следовать данные. К ошибкам смешивания близки по результатам ошибки на- ложений. Коды команд любой программы предполагаются по- стоянными; они находятся в памяти все время, пока программы выполняются ’>. Если же в результате ошибки в программе не- которое число записывается в ячейку, в которой хранится код команды, «накладываясь» на него, мы говорим об ошибке на- ложения, или уничтожения. Ошибки наложения в программах на языке ассемблера встречаются гораздо чаще, чем в программах, написанных на других языках. Их трудно обнаружить в процессе отладки про- граммы, потому что они проявляются не в тот момент, когда выполняется ошибочная команда, а позже (иногда значительно позже), когда процессор попытается выполнить искаженную на- ложением команду. Не забывайте, что процессор понимает только машинный язык — не язык ассемблера. Например, коман- да LDX ф!233 имеет машинную форму А2 Е9, и процессор за- грузит в регистр X число 233, только если он обнаружит в со- ответствующих ячейках коды А2 Е9. Если же в этих ячейках оказывается что-то иное, процессор выполнит какую-то другую команду. Здесь может быть много разных возможностей. Если унич- тожен код Е9, регистр X будет загружен каким-то числом, от- личным от 233. Если же уничтожен код А2, будет выполняться другая команда. Можно считать удачей, если вместо А2 будет записан 0 — это код операции команды BRK. Хуже, если вместо А2 будет записан код 1-байтовой команды. Тогда Е9 будет рас- сматриваться процессором как код операции следующей команды. Код Е9 (шестнадцатеричное представление числа 233) яв- ляется одновременно кодом операции команды SBC *п. Теперь следующий за Е9 байт, который действительно является кодом операции, рассматривается как число п. Когда случается такого рода сбой, говорят, что произошло нарушение границ команд. ’> Из этого правила имеются исключения; см. статьи 78, 80 и 81,
168 Статья 45 Заметьте, что, если п представляет собой код операции 1-байто- вой команды, границы команд восстановятся, однако процессор успеет выполнить несколько незапланированных и, возможно, пагубных для программы команд. Если же п — часть кода 2- или 3-байтовой команды, то нарушение границ команд сохранится (по крайней мере на некоторое время). Из сказанного можно извлечь два полезных урока. Во-пер- вых, если программа на языке ассемблера выполняет неправиль- ное действие, то причиной этого могут быть гораздо более ран- ние строки программы, вызвавшие ошибку наложения. Никогда не считайте, что вы точно знаете причину ошибочного выполне- ния программы на языке ассемблера; очень часто в этом винов- на ошибка, встретившаяся гораздо раньше (иногда ее называют отложенной ошибкой). Во-вторых, постарайтесь еще перед отладкой устранить на- ложения. Наиболее обычной причиной наложения являются за- пись данных в массив, когда значение индекса вышло за допус- тимые пределы, т. е. стало слишком большим или слишком ма- леньким. Если, например, вы записываете что-то в T(J), и мас- сив Т лежит в границах от Т(1) до Т(100), но J случайно ока- залось в какой-то момент больше 100 либо меньше 1, то вы мо- жете разрушить программные строки в результате наложения. Если вы часто совершаете ошибку такого рода, вам полезно написать несколько команд, которые будут проверять значения каждого индекса перед его использованием и фиксировать его выход из установленного диапазона. Это немного замедляет программу, но заметно ускоряет процесс отладки. Наложение может привести к уничтожению не только ко- манд, но и данных. Если у вас есть массив Т, за которым в па- мяти хранятся какие-либо константы (например, символьная строка), и при записи в массив индекс вышел за границы мас- сива, вы скорее всего разрушите символьную строку. Позже, когда вы захотите вывести эту строку на экран, вы получите изображения совсем не тех символов. В процессе отладки программы вы можете контролировать наложения с помощью команд перемещения и проверки монито- ра Эпл (см. конец статьи 48). Еще одна похожая ошибка возникает при неправильном за- дании стартового адреса. Если ваша программа начинается с секции данных и вы начинаете выполнение программы с ее пер- вого адреса, процессор будет пытаться обрабатывать данные, как если бы они были командами, точно так же как и при ошиб- ке смешивания. Чтобы избежать этой ошибки, следует начинать программу с программной секции, либо перед данными поста- вить команду JMP START, а метку START присвоить первой программной строке.
Ошибки смешивания, наложения и стартового адреса 169 Упражнения >}< 1. Укажите в приведенной ниже программе все ошибки сме- шивания (учтите, что не все случаи чередования программных строк и данных в этой программе ошибочны): ORG $0В00 овоо AD 03 OB MIX LDA I овоз I DFS 11 0B04 DO 08 BNE MIX2 0В06 J DFS !1 0В07 AD 06 OB LDA J COUT EQU $FDED ОВОА 4С 11 OB JMP MIX3 0B0D к DFS 11 OBOE 20 ED FD MIX2 JSR COUT 0В11 60 MIX3 RTS 0В12 L DFS !1 END 2. Приведенная ниже подпрограмма, вызываемая несколько раз в процессе выполнения основной программы, служит для присвоения значения 2 элементам массива от Т(1) до Т(100). Представим, что вместо команды STA Т—!1,Х мы написали STA Т, X, что послужило причиной ошибки наложения. В чем заключается эта ошибка? Для детального описания ситуации используйте форму машинного языка, как это показано ниже. В какой момент проявится эта ошибка, и в какой форме? Игно- рируйте побочную ошибку, которая заключается в том, что пер- вый байт массива Т так и останется без присвоения. ORG $0820 0820 T DFS 1100 0884 64 N BYT 1100 0885 AE 84 08 SUBR LDX N 0888 A9 02 LDA #!2 088A 9D IF 08 LOOP STA T-ll, X 088D CA DEX 088E DO FA BNE LOOP 0890 60 RTS 3. Ответьте на оба вопроса упр. 2 применительно к подпро- грамме, приведенной ниже. Подпрограмма присваивает значе- ние 233 элементам массива от Т(1) до Т(100).
170 Статья 46 ORG $0870 0870 Т DFS 1100 08D4 А2 64 START LDX #1100 08D6 А9 Е9 LDA #1233 08D8 9D 6F 08 LOOP STA T —11, X 08DB СА DEX 08DC DO FA BNE LOOP 08DE 60 RTS Статья 46 Команды программы LISA Каждый, кто когда-либо использовал Бейсик, знаком с командами Бейсика (LIST, RUN, SAVE и др.), которые отлича- ются от предложений Бейсика (например, N=I-|-J—К). Про- грамма LISA также имеет команды, со многими из которых вам следует познакомиться; однако идеи, лежащие в основе команд LISA, иные1). В Бейсике строки имеют номера, расположенные в порядке возрастания. Вставка новой строки между строками с номерами i и /. требует просто присвоения ей номера, находящегося меж- ду номерами i и /. В языке ассемблера, однако, номера строк не являются элементами языка. Ассемблер LISA не использует номера строк при обработке текста программы. Номера строк, таким образом, являются просто порядковыми номерами строк в буквальном смысле этого слова: первая строка программы всегда имеет номер 1, вторая — номер 2 и т. д. Поскольку не может быть строки с номером Р/г для вставки новых строк при модификациях программы предусмотрена спе- циальная команда I (от insert — вставить). Аналогично этому удаление строк осуществляется командой D (от delete — уда- •> Мы опускаем описание того, как запускается LISA, потому что эта процедура может отличаться в зависимости от конкретной конфигурации ЭВМ Эпл.
Команды программы LISA 171 лить). (Программа LISA предлагает вам ввести команду с по- мощью символа-подсказки !, выводимого на экран терминала.) Вы можете использовать команду In, чтобы вставить любое количество строк перед строкой с номером п (не после строки п— не забывайте об этом!). Вы можете также использовать просто I, чтобы вставить любое количество строк в конце своей программы. В обоих случаях, закончив вставку новых строк, введите control-E и возврат каретки. Вы можете использовать команду D т, чтобы удалить строку т. Вы можете использовать команду D пг, п, чтобы удалить сра- зу несколько строк с номерами т, т+1 и т. д. до п включитель- но. Если вы хотите заменить строки (т. е. удалить их, и затем вставить на их место новые), введите команду М (от modify — изменить) вместо D; в зависимости от того, требуется ли вам заменить одну строку или несколько, используются формы М т или М т, п. В программе LISA используется известный прием, позволяю- щий рассматривать создание новой программы как частный случай изменения уже существующей. Действительно, вставка новых строк в программу, в которой еще нет ни одной строки, абсолютно эквивалентна созданию новой программы! Поэтому первое, что вы должны сделать после загрузки программы LISA — это ввести команду I (без номера строки) и затем вво- дить новую программу (помните, однако, что каждая команда программы LISA — даже просто I — требует после себя возвра- та каретки). Скорее всего вам захочется увидеть свою программу (или часть ее) на экране; это называется выводом листинга. Вы можете ввести команду L для вывода на экран всей вашей про- граммы; или L т для вывода строки т; или L т, п для вывода строк с номерами т, m-f-l и т. д. до п включительно. (Вы може- те также направить вывод на печатающее устройство, как в Бей- сике, и использовать команду L для получения печатной копии, т. е. листинга программы, напечатанного на бумаге.) Когда, по вашему мнению, программа готова к выполнению, присвойте ей имя файла (например, TEST7) и сохраните на диске, введя команду SAVE (сохранить) с указанием имени файла (в нашем примере SAVE TEST7). Имя файла, как и имя переменной, должно начинаться с буквы, и в нем может быть до 30 сим- волов. В отличие от Бейсика вы не можете выполнить свою про- грамму немедленно. Сначала ее надо ассемблировать, т. е. транс- лировать с языка ассемблера на машинный язык. Это делается с помощью команды ASM (от assemble — ассемблировать). Эта команда выводит на экран листинг, содержащий обе формы текста программы — на языке ассемблера и на машинном язы-
172 Статья 46 ке. Обычно листинг слишком длинен, чтобы поместиться на эк- ране, а ассемблирование идет так быстро, что вы не успеваете просмотреть появляющийся на экране текст. Нажмите клавишу пробела — это остановит процесс вывода листинга; для продол- жения нажмите клавишу пробела еще раз. Это можно повто- рить многократно1). Многие делают значительное количество ошибок, вводя текст с клавиатуры, и ЭВМ Эпл предоставляет несколько способов их исправления. Вы всегда можете использовать клавишу возвра- та (-<-), чтобы вернуться и ошибочный текст заменить на пра- вильный. Если вся строка оказалась ошибочной, введите cont- rol-X. (Этими клавишами можно пользоваться и при вводе текс- та в программу с помощью подпрограммы GETLNZ, описанной в статье 25.) Предположим теперь, что вы ввели длинную строку, и внут- ри ее есть лишние символы. Вы, конечно, можете ввести cont- rol-X и затем скорректированную строку; не исключено, однако, что вы снова сделаете подобные ошибки. Программа LISA поз- воляет вам использовать несколько управляющих клавиш для исправления ошибок без повторного ввода всей строки. Прежде всего с помощью клавиши возврата (-«-) вернитесь к первому из лишних символов и нажмите control-K. Тем самым вы удалите этот символ; последующие нажатия control-K позволят удалить следующие символы. Когда вы подойдете к символу, который уже не является лишним, нажмите клави- шу столько раз, сколько нужно, чтобы дойти до конца стро- ки21. Символы control-O, control-L, control-J, control-K обознача- ют «вверх», «вниз», «влево» и «вправо» соответственно; они перемещают курсор (мерцающий на экране) в указанных на- правлениях. Используя эти клавиши, вы можете произвольно перемещать курсор по экрану (помните, что control-0 — это не control-нуль). Имеется также возможность вставлять символы внутрь стро- ки. Вернитесь с помощью клавиши возврата на то место, куда нужно вставить символы; используя управляющие символы О, L, J и К, переместите курсор на свободное место экрана; введи- те требуемые символы; снова, используя управляющие символы, верните курсор на первую после вставки позицию в строке; на- конец, с помощью клавиши вернитесь к концу строки, как и при удалении. *> LISA 2.5 остановит вывод листинга, если при ассемблировании встре- тилась ошибка. 2> ЭВМ. Эпл имеет клавишу повторения, помеченную REPT (от «repe- at»— повторение). Одновременное нажатие клавиш повторения и любой дру- гой клавиши (например, ->) равносильно многократному нажатию на эту клавишу.
Команды программы LISA 173 Упражнения 1. Пусть мы работаем с такой программой: 1 LDA Р 2 STA Q 3 LDA R 4 STA S Как изменится первоначальный текст программы при исполь- зовании приведенных ниже команд программы LISA? Приведи- те для каждого пункта новые тексты программ с указанием по- рядковых номеров строк, начиная с 1. а) I 3 STA Т * б) D 2,4 в) М 2 STA Т 2. Пусть исходная программа совпадает с приведенной в упр. 1. Напишите последовательности команд LISA (максималь- но короткие), чтобы получить вместо исходной следующие про- граммы: * а) 1 LDA М 2 STA N 3 LDA Р 4 STA Q 5 LDA R 6 STA S б) 1 LDA Р 2 LDA R 3 STA S в) 1 LDA Р 2 STA Q 3 LDA R 4 STA Т 3. Пусть исходная программа совпадает с приведенной в упр. 1. Что будет выведено на экран после выполнения следую- щих команд LISA:
174 Статья 47 D 1 D 1 I 2 STA T LDA U M 3 LDA V L Статья 47 Пошаговое выполнение и трассировка Теперь вы можете начать отладку с помощью программы ЭВМ Эпл, называемой монитором (экран дисплея Эпл тоже иногда называют монитором, но в настоящей книге слово «мо- нитор» всегда будет относиться к программе, которая является сердцем системы Эпл). Чтобы начать работать с монитором, вы вводите команду BRK, которая является командой программы LISA и одновре- менно машинной командой. Монитор выводит на экран символ который является символом-подсказкой монитора (как ! для программы LISA), требуя от вас ввода команды монитора. Если ваша программа начинается с шестнадцатеричного 0800, вы мо- жете ввести 0800G (или 800G) 1>, и монитор перейдет на выпол- нение вашей программы (G обозначает go to — перейти). Обычно, впрочем, вы не будете вводить 0800G, пока програм- ма полностью не отлажена. Вместо этого вы используете поша- говое выполнение и трассировку (иногда в сочетании с отлад- кой с помощью точек останова, о чем речь будет идти в следую- щей статье). Пошаговое выполнение означает выполнение про- Ч Не вводите $0800, эта команда не будет принята. Числа в командах монитора всегда рассматриваются, как шестнадцатеричные, поэтому символ ;$ не используется.
Пошаговое выполнение и трассировка 175 граммы команда за командой или, как говорят, шаг за шагом. Выполнив очередной шаг, монитор выводит на экран: 1) адрес команды; 2) машинный код команды; 3) мнемоническое обозначение программной строки на язы- ке ассемблера; 4) содержимое регистров в шестнадцатеричной форме после выполнения команды. Все это происходит, если вместо G вы вводите S (от step — шаг). Если, например, ввести 0800S на экране появится: 0800- AD ЗС 08 LDA $083С А—01 Х=00 Y=5F Р=30 S=F8 Здесь команда LDA $083С, машинный код которой составля- ет AD ЗС 08, находится по адресу 0800; регистр А содержит 1, регистр X — 0, а регистр Y — шестнадцатеричное 5F. (Кроме этого, выводится содержимое еще двух регистров, S и Р, но про них мы узнаем в статьях 60 и 67 соответственно.) Заметьте, что команда LDA $083С в тексте вашей про- граммы выглядела, возможно, как LDA W или что-то вроде этого с использованием символического адреса (W). Видимо, в ячейке W была записана 1, раз регистр А после загрузки в него значения W содержит 1. После ассемблирования програм- мы вы можете выписать из листинга шестнадцатеричные адре- са всех переменных. Если вы хотите посмотреть, каково содер- жимое байта по адресу 083С, просто введите 083С (и возврат каретки), и искомый код в шестнадцатеричной форме будет вы- веден на экран. Это можно выполнить для любого адреса. Ана- логично, если вы хотите просмотреть содержимое целого мас- сива, скажем с адреса' 0884 по адрес 08DB, введите просто 0884.08DB (обратите внимание на знак точки), и содержимое всех байтов массива будет выведено на экран в шестнадцате- ричной форме. Это можно выполнить для любой пары адресов Вводя в составе команды несколько символов S, можно вы- полнить любое число шагов. Так, команда 0800SSSSS выполнит 5 машинных команд с начала программы. Каждый шаг будет сопровождаться выводом на экран описанных выше двух строк; всего, таким образом, будет выведено 10 строк. Перед символом S указывается адрес, с которого начинает- ся выполнение; если же мы хотим продолжить выполнение с то- го места, на котором остановились, то адрес можно опустить. Так, например, можно несколько раз подряд вводить команду » Часто таким способом выводят всю программу, включая команды и данные; в этом случае полученная таблица называется дампом.
176 Статья 47 SSSSS; каждый раз будут выполняться следующие пять шагов с выводом на экран соответствующих данных. Цель пошагового выполнения программы — выяснить, пра- вильно ли выполняются вычисления. Об этом можно судить по следующим признакам: 1) содержимое регистров после выполнения каждой команды; 2) действительный порядок выполнения команд; 3) содержимое ячеек и массивов ячеек в памяти, которое можно получить, вводя их адреса, как это было показано выше. В процессе пошагового выполнения легко потерять представ- ление о том, в каком месте программы вы находитесь. Если это произошло, но вы только что получили на экране сообщение о последнем шаге с адресом команды ХХХХ, введите команду XXXXL, чтобы получить распечатку этой и нескольких следую- щих (до заполнения экрана) команд в форме языка ассембле- ра и машинного языка. Эту распечатку можно сопоставить с печатной копией программы. Трассировка (tracing) представляет собой пошаговое выпол- нение в автоматическом режиме. Команда 0800Т, например, воспринимается как указание адреса 0800 с бесконечным чис- лом символов S за ним. Шаги (включая вывод информации на экран) выполняются в этом случае бесконечно; этот процесс можно приостановить и снова пустить нажатием клавиши про- бела, как это описывалось в предыдущей статье применительно к выводу листинга программой LISA. После того как вы локализовали ошибки в программе, вы должны выйти из монитора, передать управление программе LISA1) и затем ввести команду LOAD с указанием имени про- граммы (LOAD TEST7, если пользоваться примером предыду- щей статьи). В этом случае программа с диска переписывается в оперативную память, т. е. выполняется загрузка программы (не путайте загрузку программы с загрузкой регистра — эти по- нятия не имеют ничего общего). Теперь вы можете использовать для исправления ошибок команды I, D и М программы LISA, как это было описано в предыдущей статье. Расширенная версия команды LOAD позволит вам объеди- нить несколько программ в одну, если перед этим вы их сохра- нили на диске. Пусть они имеют имена PROG1, PROG2, PROG3 и т. д. Вы загружаете программу PROG1 командой LOAD PROG1, как и раньше, а затем присоединяете остальные программы. Так, команда АР PROG2 присоединит вторую про- грамму, АР PROG3 — третью и т. д. (АР является сокращением от append — присоединить). » Это делается с помощью команд 7003G (LISA 1.5), 6003G (LISA 2.5, 48К) или E003G (LISA 2.5, 64К).
Пошаговое выполнение и трассировка 177~ Описание всех команд программы LISA, используемых в этой книге, приведено в табл. П13. Описание всех команд мо- нитора Эпл, используемых в этой книге, приведено в табл. П14. Более полный список команд монитора Эпл можно найти в опи- сании ЭВМ Эпл. Упражнения # 1. Представьте, что мы трасеируем программу, содержащую следующие строки: LDY #!100 INY и после выполнения некоторого шага (конкретно, первой из при- веденных команд) на экран выведено следующее сообщение: 0838- А2 64 LDY #$64 A=8D Х=32 Y=64 Р=30 S = F8 Что будет выведено на экран после следующего шага (считай- те, что содержимое регистров Р и S не изменяется)? 2. Представьте, что мы трассируем фрагмент программы LDA ALPHA1 STA ВЕТА1 LDA ALPHA2 STA ВЕТА2 LDA ALPHA3 BMI ALFNEG и после выполнения 4 шагов на экране появилась следующая информация: 08F4- AD 31 09 LDA $0931 A=6F X=00 Y=64 P=31 S=F4 08F7- 8D 6B 09 STA $096B A=6F X=00 Y=64 P=31 S=F4 08FA— AD 32 09 LDA $0932 A=FF X=00 Y=64 P = 31 S=F4 08FD- 30 08 BMI $0907 A=FF X=00 Y=64 P=B1 S=F4 С помощью какой команды монитора можно вывести на экрав шестнадцатеричное содержимое ячейки ALPHA2? 12—589
178 Статья 48 >|< 3. Приведенная ниже программа должна сосчитать число €итов со значением 1 в ячейке ВЕТА и поместить это число в регистр Y. Программа, однако, содержит ошибку. LDA ВЕТА LDX #!8 LDY #10 ASL LOOP ВСС LOOP1 INY ASL LOOP1 DEX BNE LOOP В процессе пошагового выполнения программы последние четы- ре шага привели к появлению на экране следующей информа- ции: 0920— 90 02 BCC $0924 A=D0 X=05 Y=03 P=30 S=F6 0924— CA DEX A=D0 X=04 Y=03 P=30 S=F6 0925 — DO F9 BNE $0920 A=D0 X=04 Y=03 P=30 S=F6 0920 — 90 02 BCC $0924 A=D0 X=04 Y=03 P=30 S=F6 Найдите ошибку в программе. Как это можно сделать, изучая результаты выполнения каждого шага? (Подсказка: обратите внимание на содержимое регистра А.) Статья 48 Отладка с точками останова Пошаговое выполнение и трассировка хороши для коротких программ и для программ, не содержащих обращений к про- граммам COUT, RDKEY и др.; более универсальным способом -является отладка с точками останова.
Отладка с точками останова 179» Точка останова — это просто точка в программе, на которой вы можете временно остановить выполнение, просмотреть содер- жимое нескольких регистров и ячеек памяти и затем продолжить выполнение программы. Это осуществляется путем включения в программу в точке останова команды внутреннего прерыва- ния (BRK— шестнадцатеричное 00), по которой управление передается монитору Эпл. Отладка с точками останова использует еще одну возмож- ность монитора, именно возможность изменять содержимое яче- ек памяти. Для того чтобы поместить число hh (шестнадцате- ричное) в ячейку с адресом хххх, мы вводим команду xxxx:hh, (или просто :hh, если мы только что вывели содержимое ячей- ки хххх). Как и раньше, при указании адреса хххх не исполь- зуйте символ $. Для того чтобы поместить числа аа, bb, сс и т. д. в массив, начинающийся с адреса хххх, мы вводим коман- ду хххх:аа bb сс (и т. д.). В результате аа окажется по адресу- хххх-, bb — по адресу хххх-^1-, сс — по адресу хххх-{-2 и т. д. Пусть теперь вы хотите установить точку останова перед, командой с адрессом хххх. Вы вводите хххх и, получив от сис- темы ответ hh, вводите :00, чтобы поместить по этому адресу нуль (BRK). Когда программа дойдет до этой точки, будет вы- полнена команда с кодом операции 00, т. е. BRK; в результате управление будет передано монитору. Чтобы удалить эту точку останова, введите команду xxxx:hh с тем же hh, которое вы по- лучили ранее (т. е. с кодом операции, который находился по адресу хххх перед установкрй точки останова). Теперь команда ххххО запустит программу снова. Если вы знаете, как выполняется ваша программа (т. е. ка- кова последовательность выполняемых команд в любой момент времени), то процесс отладки с точками останова выглядит следующим образом. 1. Выберите команду, до которой, как вы полагаете, про- грамма будет выполняться правильно; установите в этом месте- точку останова; выполните программу вплоть до этой точки (ес- ли программа начинается с адреса хххх, введите, как обычно, xxxxG и подождите, пока не выполнится команда BRK, как это описано выше). 2. Просмотрите содержимое регистров и различных ячеек памяти, чтобы убедиться, что программа работает правильно. Если это не так, перейдите к шагу 4; в противном случае уда- лите только что установленную вами точку останова и устано- вите новую точку останова, опять выбрав команду1), до кото- 1 Если вы хотите установить ту же точку останова, то удалите точку ос- танова, выполните один шаг (с помощью команды S) и затем снова устано- вите точку останова. Объяснение дано в упр. 2.) 12*
180 Статья 48 рой, как вам кажется, программа будет выполняться правильно. 3. Запустите программу с только что удаленной вами точки •останова, введя команду xxxxG (если для удаления точки оста- нова была использована команда хххх: hh). Заметьте, что в этом случае выполнение начнется с первой невыполненной ранее команды. Когда программа дойдет до следующей точки оста- нова, вернитесь к шагу 2. 4. Теперь вы приблизительно знаете, где находится ошибка. До, скажем, точки останова а ваша программа работала пра- вильно; в точке b она уже работает неправильно. Если вы мо- жете обнаружить ошибку, изучая печатную копию вашей про- граммы, особенно конкретное сомнительное место (и зная, как должен работать этот участок программы), исправьте ошибку и повторите ассемблирование. В противном случае установите снова точку останова а, выполните программу от начала до точ- ки а, удалите точку останова и продолжите выполнение про- граммы в пошаговом режиме, как это было описано в предыду- щей статье. Если вы не знаете, как выполняется ваша программа, напри- мер если в результате ошибок в программе выполняется не та команда, либо происходит переход не на ту- точку, можно вос- пользоваться описанной выше процедурой отладки с некоторы- ми изменениями. На шаге 1 выберите несколько возможных то- чек останова; то же сделайте на шаге 2. Кроме того, на шаге 2 удалите все точки останова, до которых, по вашему убежде- нию, программа не дойдет, когда вы ее запустите на шаге 3. Далее следите не только за содержимым регистров и памяти, но и за тем, на какую точку останова вышла программа, чтобы убедиться, что это запланированная в данном случае точка ос- танова. Некоторая трудность возникает при отладке с точками оста- нова программ, содержащих обращение к подпрограмме <jETLNZ, которая использует входной буфер, расположенный по адресу 0200. Трудность заключается в том, что монитор Эпл использует тот же входной буфер, поэтому когда вы вводите команды (например, SSSSS), они накладываются на символы, находящиеся в буфере. Чтобы обойти это затруднение, напиши- те цикл, перемещающий эти символы в другой массив, и уста- новите точку останова в вашей программе после этого цикла. Иногда, используя методику отладки с точками останова, можно по ошибке попасть в бесконечную петлю. В этом случае нажмите клавишу RESET (в некоторых моделях Эпл —control- RESET). Это вызовет прерывание. Команды М. (от move — переместить) и V (от verify — про- верить), упомянутые в конце статьи 45, как средство проверки на наложение, имеют форматы
Отладка с точками останова 181 сссс<.аааа.ЬЬЬЫЛ. (команда М) cccc<Zaaaa.bbbbV (команда V) Команда М перемещает вашу программу, которая занимает память с адреса аааа по адрес bbbb, в неиспользуемую область памяти, начиная с адреса сссс. Команда V, выполняемая позже, сравнивает обе программы. Если в программах обнаруживаются различия, на экран выводятся адреса различающихся ячеек вместе с их содержимым. (Однако здесь вам следует проверить, в какой именно версии программы произошло наложение — в оригинале или в копии.) Упражнения 1. Команда INY расположена по шестнадцатеричному адре- су 0А03. Приведите для каждого из перечисленных ниже пунк- тов команду монитора Эпл, которая: а) установит точку останова по этому адресу; >|с б) удалит точку останова по этому адресу; в) продолжит выполнение после останова по этому адресу. э|с 2. Программист установил точку останова в большом цик- ле, написав команду ВР1 BRK (ВР1 — метка) в программе на языке ассемблера, и выполнил программу до точки ВР1. Теперь желательно продолжить вычисления, начав со следующей за BRK командой так, чтобы процессор выполнил один шаг цикла и дошел снова до точки ВР1. Это можно сделать, введя коман- ду УУУУ&, где уууу=хххх±1, а хххх—адрес ВР1. Докажите, что этого нельзя сделать, если точка останова была установлена в точке ВР1 командой монитора хххх:00, а не командой язы- ка ассемблера BRK. (Подсказка: попробуйте точно описать, как вы это сделаете. Можно предложить несколько правдоподобных вариантов, но ни один из них в действительности не годится.) 3. Предположим, что некто, изучающий программирование, спрашивает вас: «Можно ли установить точку останова не на байте с командой, а на байте с данными?» Как вы ответите?
182 Статья 49 Статья 49 Принцип разрыва телефонной линии и двоичный поиск Мы уже отмечали, что отладка с точками останова исполы- зуется для программ, слишком больших для отладки с помо- щью пошагового выполнения и трассировки. Для еще более длинных программ используется принцип разрыва телефонной линии. Представьте, что вам надо починить 4-мильный (1 миля = = 1609 м) участок телефонной линии, на котором имеется по- вреждение. Вы можете начать с одного конца линии и каждые 100 футов (1 фут=30,5 см) или около того проверять, дошли ли вы до повреждения, т. е. имеется ли разрыв между точкой,, где вы находитесь, и началом линии. Если линия имеет длину 4 мили, то проверка через каждые- 100 футов потребует в общей сложности свыше 200 испытаний. Число испытаний можно существенно сократить следующим об- разом. Пройдите вниз по линии 2 мили и проверьте ее на раз- рыв. Если линия работает, это говорит о том, что разрыв про- изошел во второй половине. Так или иначе, вы сократили отре- зок линии, требующий проверки, до двух миль. Теперь пройдите 1 милю по неисправному участку и проверь- те его снова. Если линия работает, значит, разрыв произошел во второй из этих двух миль; в противном случае он в первой: миле. В любом случае вы локализовали неисправность на отрез- ке длиной в 1 милю. Теперь пройдите полпути по неисправному- участку и повторяйте описанную процедуру деления неисправ- ного участка пополам. Преимущество этого метода заключается в том, что вы лока- лизуете неисправность на отрезке длиной 100 футов после всего 9 испытаний (вместо более 200). Метод можно использовать и в том случае, когда в линии несколько разрывов; найдя первый разрыв (ближайший к тому концу, с которого вы начали), вы повторяете процедуру в поисках следующего разрыва. Этот же принцип можно использовать и для отладки про- грамм. Установите первую точку останова где-то в середине программы. Когда программа выполнится до этого места (если это произойдет), проверьте содержимое регистров и памяти, что- бы убедиться, что программа до этого места работала правиль- но. Если это так, вы будете знать, что ваша первая ошибка на-
Принцип разрыва телефонной линии и двоичный поиск 183 ходится во второй половине программы, В противном случае юна в первой половине. Так или иначе, вы сократили область поиска ошибки, до по- ловины программы. Теперь установите точку останова приблизи- тельно в середине этой половины, снова запустите программу и проверьте содержимое регистров и памяти. Таким образом, вы локализуете ошибку на участке в четверть программы. Продол- жайте процедуру, деля программу каждый раз приблизительно пополам, пока вы не сократите область поиска ошибки до та- ких размеров, при которых можно воспользоваться обычной ме- тодикой отладки с точками останова. Если ошибки в программе приводят к неправильному поряд- ку выполнения программных строк, предложенный метод следу- ет, как и ранее, несколько изменить. Может, например, полу- читься, что вы установили точку останова в середине некоторо- го участка программы, но при выполнении программа проходит вообще мимо точки останова. Конечно, это все же локализует ошибку: она в первой половине этого участка. Однако вы всегда можете установить несколько точек останова, как и при обычной •отладке с точками останова. Любопытно отметить, что этот же принцип можно использо- вать для нахождения требуемой величины Q среди элементов Т(1) ... T(N) некоторой таблицы. Мы можем сравнивать Q с Т(1), затем с Т(2) и т. д.; это, однако, напоминает проверку каждых 100 футов телефонной линии. Предположим, что эле- менты таблицы Т упорядочены. Это значит, что они расположе- ны в порядке возрастания, так что Т(1) —это минимальный по величине элемент, Т (2) — следующий по величине и т. д. В этом случае мы можем искать Q в таблице, используя двоичный по- иск, для чего нам надо многократно делить таблицу Т пополам. Чтобы начать двоичный поиск, мы сравниваем Q с T(N/2). Если Q больше, чем T(N/2), то Q должно совпадать с некото- рым T(J), где J>N/2 (поскольку таблица упорядочена). Поэто- му Q находится во второй половине таблицы. В противном слу- чае элемент, равный Q, находился бы в первой половине таб- лицы (опять же вследствие того, что таблица упорядочена). Так или иначе, мы сузили область поиска до половины табли- цы. Теперь мы сравниваем Q со средним элементом этой поло- винной таблицы и затем повторяем процедуру многократно. Позже, в статье 86, мы вернемся к этому методу. Никогда не пытайтесь организовывать пошаговое выполне- ние (или трассировку) входных и выходных подпрограмм (RDKEY, GETLNZ, COUT и проч.). Для входных подпрограмм пошаговое выполнение помешает вводу символов; для выход- ных— нарушит вывод символов на экран. Кроме того, в под- программах монитора Эпл с очевидностью нет никаких ошибок.
184 Статья 49 Упражнения >{< 1. Вы, несомненно, знаете о десятичных логарифмах (по ос- нованию 10) и натуральных логарифмах (по основанию ecu cs;2,71828). В вычислительной технике оказываются весьма по- лезными логарифмы по основанию 2: log2%=«/, если 2{/=х. Та- ким образом, целые степени двух, от 1 до 64, имеют такие зна- чения логарифмов: Число 1 2 4 8 16 32 64 Логарифм (по основанию 2) 0 1 2 3 4 5 6 Промежуточные числа имеют промежуточные логарифмы (так, У2~ 1,414 имеет логарифм 0,5, поскольку 2°>5=У2). Найдите формулу, выражающую в единицах логарифмов по основанию 2 число шагов, требуемых для локализации разрыва в телефон- ной линии длиной х футов на отрезке длиной у футов методом, изложенным в тексте. 2. а) Принцип разрыва телефонной линии нельзя непосред- ственно использовать в программе, представляющей собой один очень большой цикл, выполняемый много раз. Почему? б) Предложите метод добавления к вашей программе не- скольких команд, которые позволят использовать рассмотренный принцип и в таком случае. (Команды такого рода называют ди- агностическими.) 3. а) Найдите формулу, выражающую в единицах лога- рифмов по основанию 2 число шагов, требуемых для двоичного поиска в таблице длиной п. Предположите, что поиск продолжа- ется, пока длина таблицы, после многократного уменьшения в два раза, не достигнет 2; после этого выполняются два послед- них шага, в которых анализируется каждый из двух оставшихся элементов. Дайте словесную интерпретацию вашей формулы для значений п, не являющихся степенями двух. б) Представьте себе, что отлаживается сама программа дво- ичного поиска. В этом случае рассуждения, приведенные в упр. 2, а, несущественны. Почему?
Заплаты в программах на языке ассемблера 185 Статья 50 Заплаты в программах на языке ассемблера В процессе исправления ошибок в программе вы обычно ас- семблируете программу заново после исправления каждой ошибки или небольшой группы ошибок. Если, однако, у вас под рукой нет печатающего устройства, или вы не получаете при каждом ассемблировании новый вариант печатной копии листинга, процесс корректировки программы осложняется. Предположим, что вы отлаживаете результат первого ассе- мблирования. Для этого вам потребуются адреса некоторых данных и команд в вашей программе. Если у вас есть печатный текст листинга трансляции, эту информацию нетрудно там най- ти. Если печатного текста нет, необходимые адреса можно за- писывать вручную по мере отладки. Если, например, команда LDA $tO8DA, выведенная на эк- ран в процессе пошагового выполнения, соответствует в вашей программе строке LDA R, то вы видите, что R имеет адрес 08DA. Если строка LDA R имеет метку ВЕТА и в процессе пошагового выполнения выяснилось, что команда LDA $08DA расположе- на по адресу 0838, то метка ВЕТА соответствует адресу 0838. Представьте, однако, что вы заново ассемблируете програм- му и продолжаете поиск ошибок. Ббльшая часть ранее полу- ченной вами информации будет при этом скорее всего потеряна. Если при исправлении программы вы вставили или удалили ка- кие-то строки, то адреса всех строк, следующих за исправлен- ным участком, изменятся. Это происходит потому, что ассемб- лер назначает адреса в строгом последовательном порядке (за исключением тех случаев, когда в середине программы встреча- ется псевдокоманда ORG). Обойти эти трудности можно с помощью старого приема, за- ключающегося в использовании так называемых «заплат»1*. Этот прием был разработан для вычислительных систем, на которых *> Общепринятые в английском языке термины patch (заплата), patching (наложение заплат) не имеют эквивалентов в русском техническом языке. Возможный перевод «вставка», правильный по существу, не передает от- тенка, придаваемого понятию patch — вставка, располагаемая не в теле про- граммы, а «под ней» и включаемая в ход выполнения программы с помощью команд безусловных переходов. Учитывая важность и широкую распростра- ненность этой методики, нам представляется разумным ввести в технический обиход термин «заплата», которым мы и пользуемся в настоящем перево- де.— Прим, перев.
186 Статья 50 ассемблирование отнимало значительное время и стоило доро- го, так что программисты старались не прибегать к повторному ассемблированию по экономическим соображениям. В те време- на использование заплат требовало ручной трансляции текста- исправления с языка ассемблера в машинные коды. В ЭВМ Эпл, однако, имеется возможность писать тексты заплат на языке ассемблера, что проще, надежнее и не требует ручной трансляции. Необходимость в заплате возникает, когда мы хотим вста- вить последовательность новых команд между двумя команда- ми И и Ь- Допустим сначала, что К— это 3-байтовая команда. Тогда организация заплаты потребует следующих действий: 1) вернитесь из монитора в программу LISA (введя 7003G, или 6003G, или E003G, как это было указано в конце статьи 47);. 2) выведите текст вашей программы на экран. Если она не распечатывается должным образом, загрузите ее снова коман- дой LOAD (снова см. статью 47); 3) замените команду L на команду перехода в точку, лежа- щую за вашей программой (как раз перед предложением END). Заметьте, что в этом случае мы заменяем 3 байта на 3. байта, так как команда JMP также содержит 3 байта; 4) в точке, на которую происходит переход, поместите ко- манду И, за ней — новые команды, а за ними — команду пере- хода к h (эти команды и называются заплатой). При такой операции адреса всех остальных команд и данных останутся не- изменными; 5) повторите ассемблирование и продолжайте отладку. Если команда L не является 3-байтовой, мы должны найти другой способ замены п байт, расположенных перед командой 11, на другую последовательность из п байт. Вместо одной ко- манды h возьмите две или три команды перед командой Ь. Ес- ли они в сумме занимают 3 байта, замените их командой пере- хода, как и на шаге 3. Если они занимают 4 или 5 байт, замени- те их командой перехода, за которой следуют 1 или 2 «бес- смысленных» байта (команда BYT !0 вполне подойдет). Теперь уже в заплату должны, конечно, войти эти две или три изъятые (на шаге 4) команды, а не одна команда L, как раньше. Методика заплат используется и для удаления команд. Ес- ли требуется удалить один байт, замените его на NOP, 1-бай- товую команду, которая ничего не делает (NOP обозначает по operation — операция отсутствует). Если удаляются 2 бай- та, замените их двумя NOP. Конечно, так можно поступить для любого числа байтов, но проще заменить первые 3 байта коман- дой перехода в точку, расположенную за удаляемыми байтами (с другой стороны, такая команда может впоследствии ввести, вас в заблуждение).
Заплаты в программах на языке ассемблера 187 Никогда не оставляйте заплаты в отлаженной программе. Как только вы убедитесь в том, что программа работает, прове- дите ее окончательное ассемблирование, исправив текст про- граммы обычным образом и удалив заплаты. Интересная невыдуманная история с заплатой произошла на корабле ’’Аполлон**, облетающем Луну. Бортовая ЭВМ вы- дала сигнал тревоги и отказалась выполнять дальнейшие вы- числения. Космонавты быстро обнаружили, что неисправен ава- рийный датчик, дающий неправильный отсчет. Программист на Земле написал текст заплаты для программы обработки аварий- ных сигналов, изменяющий ее так, чтобы конкретный аварийный сигнал не считался аварийным. Эта заплата была написана пря- мо в кодах, и необходимые изменения текста бортовой програм- мы на машинном языке были продиктованы космонавтам по ра- дио. Программа с заплатой благополучно довела космонавтов до Земли. Упражнения 1. а) Рассмотрите такую последовательность команд: LDX #18 LOOP LDA BITS-11, X LSR CMP #$58 BNE ERROR JMP PATCHI LOOP1 DEX BNE LOOP В программе имеется ссылка на заплату, помещенную в конце программы и имеющую такой вид: PATCH 1 LDA BITS—II, X LSR ROR BIN JMP LOOP1 Перепишите программу так, чтобы удалить заплату; другими словами, включите команды заплаты в основной текст програм- мы, расположив их там, где им надлежит находиться1). о Можно заметить, что смысл заплаты состоит во включении дополни- тельных команд LDA и LSR так, чтобы состояние флажка переноса, устанав- ливаемое командой LSR (и нарушаемое наложением результата выполнения .команды СМР), можно было бы использовать в команде ROR.
188 Статья 50 б) Какие команды и метки становятся ненужными при вы- полнении изменений программы по п. 1, а? >|< 2. Напишите на языке ассемблера текст заплаты, включаю- щей команды BNE ВЕТА INC Q+!2 перед меткой ВЕТА в такую последовательность строк: LDA Р CLC ADC Q ВСС ВЕТА INC Q-H1 BETA STA Q Приведите тексты заплаты и измененной программы. >|< 3. Напишите на языке ассемблера текст заплаты для удале- ния команды ASL и включения новой команды ASL вслед за командой TYA в такой последовательности строк: LDX LDY #!0 #!0 GAMMA LDA BITS-11, X LSR TYA BCC DELTA CLC ADC DELTA ASL TAY INX CPX #!8 BNE GAMMA STY BIN Приведите тексты заплаты и измененной'программы. Будьте очень внимательны при подсчете байтов, чтобы не изменить ад- реса команд вне заплаты. Задача 1 для решения на ЭВМ Используются подпрограммы GETLNZ, MULT, DECI и DECOZ.
Вызов на языке ассемблера из программ на Бейсике 189> Нам пора начинать писать программы для ЭВМ. Наше первое упражнение будет в большей степени упраж- нением по вводу текста, использованию редактора, ассемблиро- ванию и исправлению ошибок. Собственно, программирования здесь будет очень мало. Вам надлежит написать программу, которая: 1) вводит число, используя подпрограммы GETLNZ и DECI; 2) записывает это число в память и вводит таким же обра- зом второе число; 3) перемножает эти числа, используя подпрограмму MULT; 4) выводит на экран результат, используя подпрограмму DECOZ; 5) возвращается к шагу 1. Предполагается, что, составляя текст программы, вы вве- дете с клавиатуры программу MULT (статья 39), а также про- граммы DECI и DECO (статьи 62 и 63). Это составит значи- тельный объем работы по вводу с клавиатуры, и в этом есть большой смысл. Ввод с клавиатуры написанных вами длинных программ и последующий контроль введенного текста с целью обнаружения ошибок ввода — абсолютно необходимый элемент работы программиста. Заметьте, что, введя программы MULT,. DECI и DECO, вы должны проверить все без исключения ко- манды в этих программах. После того как вы выполните упражнение, не уничтожайте файл; в последующих упражнениях на программирование вы сможете использовать эти три подпрограммы. Статья 51 Вызов программ на языке ассемблера из программ на Бэйсике Если вы отладили программу на языке ассемблера, вы мо- жете видоизменить ее таким образом, что она станет подпро- граммой, которую можно вызвать из программы на Бейсике. Делается это следующим образом.
190 Статья 51 1. Начните свою программу с псевдокоманд ORG $8000 й OBJ $0800 (OBJ обозначает object code — объектный код). Когда ассемблер LISA оттранслирует исходный код, или •исходную программу, написанную на языке ассемблера, он по- местит объектный код, или объектную программу (результат своей работы на машинном языке), в память, начиная с адреса $0800 (этого требует псевдокоманда OBJ $0800). Однако программа не будет выполняться, пока ее команды и данные не будут перемещены в памяти в область, начинающуюся с адре- са 8000*) (ниже, в п. 5, мы увидим, зачем это нужно). 2. В начале своей программы на языке ассемблера напиши- те JSR IOSAVE (при этом надо иметь определение IOSAVE JEQU $FF4A). Это сохранит содержимое всех регистров. В конце своей •программы напишите JMP IOREST (при этом надо иметь опре- деление IOREST EQU $FF3F). Это восстановит содержимое регистров и обеспечит возврат. Не забудьте, что следует писать JMP IOREST, а не JSR IOREST, потому что программа IOREST сама обеспечит выход из подпрограммы. Все эти действия необ- ходимы, так как Бейсик предполагает, что ваша программа на языке ассемблера сохраняет и восстанавливает все регистры. 3. В листинге ассемблера отметьте последний адрес, исполь- зуемый вашим объектным кодом. Если ваша программа имеет длину не более 1000 (шестнадцатеричное) байт, этот адрес бу- дет иметь вид 8ddd, при этом число, образованное тремя послед- ними цифрами ddd, плюс единица составит шестнадцатеричную длину п вашей программы2). 4. Сохраните ваш объектный код командой программы LISA D В SAVE name, А $800, L$n где D обозначает control-D; name — это имя программы; п — ее длина (см. выше). Символ control-D говорит о том, что испол- няется команда операционной системы Эпл DOS (DOS от disk operating system — дисковая операционная система, ДОС). В данном случае командой является BSAVE (от binary save — ’> Если, например, ваш исходный код содержит команду iLDA W, а ячей- ка W расположена по адресу 8150, т. е. через 150 (шестнадцатеричное) байт после 8000, объектный код этой команды будет AD 50 81. Предположим, что эти три байта имеют адреса 809С, 809D и 809Е. После ассемблирования ва- шей программы ассемблером LISA эти 3 байта (AD, 50 и 81) будут записа- ны по адресам 089С, 089D и 089Е, но сами байты не изменятся; в частности, они не превратятся в AD 50 09 (выражая адрес 0950, т. е. на 150 байт боль- ше, чем 0800). 2> Плюс единица потому, что между адресами 8000 и 8000-j-n (включи- тельно) расположено п-|-1 байт.
Вызов на языке ассемблера из программ на Бейсике 19Т сохранить двоичный код; употребление в данном случае терми- на «двоичный» не совсем правильно, поскольку все числа в ЭВМ — двоичные, однако он отражает тот факт, что подразу- меваются целые числа и машинные коды, а не символьные ко- ды). А обозначает адрес (address) (в данном случае стартовый-' .адрес $800, куда ассемблер LISA поместил программу), и L обозначает длину (length). 5. Программы на языке ассемблера обычно располагаются в ячейках с номерами от 0800 до 17FF. Бейсик, однако, исполь- зует эти ячейки с другой целью. Собственно говоря, Бейсик ис- пользует всю наличную память, если только вы не оговорите противное с помощью предложения HIMEM. Первым предложе- нием вашей программы на Бейсике должно быть 1 HIMEM: 32767 Это заставляет Бейсик использовать только первые 32К памяти- (ячейки с номерами от 0 по 32 767 (десятичное) или с номера- ми от 0 до 7FFF (шестнадцатеричное)). Ваша программа на языке ассемблера теперь может использовать память, начиная: с адреса 8000, как и было упомянуто выше. Вторым предложе- нием вашей программы на Бейсике должно быть 2 PRINT "DBLOAD name, А$8000", где_р и пате означают то же, что и раньше. В Бейсике коман- да PRINT вывода строки, начинающейся с control-D, заставля- ет исполняться команду ДОС Эпл. В данном случае командой- является BLOAD (от binary load — загрузка двоичного кода),, которая загружает программу, ранее сохраненную командой BSAVE (вспомните, что «загрузить программу» — значит пере- нести ее с диска в оперативную память). Как и раньше, А. обозначает адрес, и на этот раз программа будет загружаться в память не с адреса $0800, где она ассемблировалась про- граммой LISA, а с адреса $8000, где ей предстоит выпол- няться. 6. Для вызова программы на языке ассемблера используй- те предложения Бейсика CALL 32 768 (обратите внимание на то, что число п в предложении Бейсика CALL п должно быть десятичным, а не шестнадцатеричным). 7. Вам может понадобиться передавать данные из програм- мы на Бейсике в программу на языке ассемблера, и наоборот. В этом случае начните программу на языке ассемблера предло- жением JMP START, за которым поместите байты с данными, а за ними — метку START, относящуюся к первой команде вашей программы. Команда JMP START занимает три байта, поэтому первый байт данных имеет десятичный адрес 32 771. Следующие байты данных имеют десятичные адреса 32 772, 32 773 и т. д.
192 Статья 51 Предложение Бейсика РОКЕ а, е помещает е в ячейку с деся- тичным адресом а; предложение К=РЕЕК (а) заносит в К содержимое ячейки с десятичным адресом а. РЕЕК (а) можно также использовать в выражениях; например, можно написать К=К+РЕЕК (а), чтобы прибавить к К то, что находится в ячейке с адресом а. Для того чтобы передать массив данных Т из программы на Бейсике в программу на языке ассемблера или наоборот, определите адрес массива в программе на языке ассемблера и преобразуйте его в десятичное число а. Теперь вы можете присвоить значение е элементу T(J) с помощью пред- ложения POKE a+J, е (в предположении, что массив Т начи- нается с Т(0)). Предложение К=РЕЕК («+•!) присвоит пере- менной К значение элемента T(J). Как и раньше, РЕЕК (a+J) можно использовать в качестве элемента более сложных пред- ложений. Величина е в выражениях РОКЕ а, е и POKE a-j-J, г может быть константой, переменной, или арифметическим вы- ражением (имеющим значение v в пределах 0^»^255). Упражнения 1. Предположим, что нам надо вызвать подпрограмму MULT из программы на Бейсике. Поскольку программа на Бейсике не может загружать регистры процессора 6502, предусмотрим два байта, MULTA и MULTX, в которых хранится содержимое ре- гистров А и X соответственно перед и после вызова MULT. Эти ячейки располагаются непосредственно за командой JMP START: ORG $ 8000 JMP START MULTA DFS !1 MULTX DFS !1 а) Напишите предложения языка ассемблера, необходимые для вызова подпрограммы MULT в такой ситуации, занесения в регистры А и X информации, требуемой для правильного вхо- да в MULT, а также получения информации из регистров А и X после выхода из MULT (не забудьте использовать подпрограм- мы IOSAVE и IOREST). б) Напишите предложения Бейсика, позволяющие выполнить операцию M=I>|<J с использованием этой подпрограммы на языке ассемблера. Считайте, что I, J и М — 8-разрядные вели- чины; в) выполните п. б), считая I и J 8-разрядными величинами, а М—16-разрядной величиной. Заметьте, что М следует пост- роить из его двух полуслов, помещенных в MULTA и MULTX последовательностью команд из и. а).
Логическое И 193 2. Массив Т в программе на языке ассемблера содержит эле- менты от Т(1) до Т(100) и имеет базовый шестнадцатеричный адрес 80Е8. а) Составьте цикл на Бейсике с предложением FOR, кото- рый переместит элементы массива с Т(1) по Т(100), располо- женного в программе на Бейсике, в этот массив (заметьте, что здесь используются два массива Т, один в программе на Бей- сике и другой в программе на языке ассемблера). б) Составьте цикл на Бейсике с предложением FOR, кото- рый переместит элементы массива с Т(1) по Т(100) обратно в программу на Бейсике. в) Выполните п. 2, а для случая, когда Т имеет 101 элемент, с Т(0) по Т(100). 3. Представьте, что у вас есть три программы на языке ас- семблера, ALPHA, ВЕТА и GAMMA, и все они вызываются из одной и той же программы на Бейсике. Вы ассемблируете ва- ши три программы совместно, и в начале программы пишите ORG $8000 JMP ALPHA JMP BETA JMP GAMMA Какое предложение Бейсика надо использовать, чтобы вызвать из программы на Бейсике программу GAMMA? Статья 52 Логическое И Теперь мы приступим к обсуждению остальных команд про- цессора 6502. Команда AND (логическое И) устанавливает в 0 отдельные биты содержимого регистра А. Чаще всего команда AND ис- пользуется с двоичной (или шестнадцатеричной) константой, ко- торая называется маской. Биты маски, установленные в 0, ука- 13—589
194 Статья 52 зывают, какие именно биты операнда устанавливаются в 0. Так, команды AND #%01111110 или AND #$7Е устанавливают в 0 первый и последний биты содержимого ре- гистра А. Команда AND относится к числу команд побитовой обработ- ки данных. Это значит, что состояние любого бита содержимо- го регистра А после действия этой команды определяется ис- ключительно его исходным значением и значением соответству- ющего бита маски1). Обозначим k-й бит маски Рк, а k-й бит со- держимого регистра А — Qk (перед действием команды AND) и Rk (после действия команды AND). Тогда Rk определяется сле- дующим образом: 0 1 Qk = 0 Qk = 1 рк = 0 Rk = 0 Rk = 1 Pk = 1 Rk = 1 Rk = 1 ига кратко 00 1 1 1 1 Знакомые с математической логикой узнают в правой табли- це таблицу истинности для логического И, чем и объясняется название команды. Если 0 обозначает «ложь», а 1 — «истину», тотаблица описывает логическое выражение R = P И Q,так как R истинно ( = 1) только в том случае, если и Р и Q оба ис- тинны. Команда AND устанавливает флажок нуля, и это дает воз- можность использовать AND для проверки отдельных разрядов ячеек памяти. Например, последовательность команд LDA Q AND #%00001000 BEQ BZERO обеспечивает переход на метку BZERO, если в ячейке Q чет- вертый разряд с правой стороны равен 0. В этом случае и в регистре А четвертый разряд справа будет равен 0, а все ос- тальные разряды регистра А будут установлены в 0 командой AND. В результате в регистре А будет 0, что установит фла- жок нуля, проверяемый командой BEQ. Команда AND вместе с командами сдвига используется для загрузки в регистр А отдельных полей заданных байтов. Рас- смотрим в качестве примера байт, разделенный на три поля (рис. 52.1). На этом рисунке представлен формат кодов опера- > Это не имеет место, например, для сложения, когда на состояние каж- дого бита содержимого регистра А влияет еще и признак переноса от сло- жения битов, находящихся справа от рассматриваемого.
Логическое И 195 ций восьми команд процессора 6502: LDA, STA, ADC, SBC, СМР самой AND и еще двух команд, ORA и EOR, которые мы рассмотрим в следующих статьях. Каждой из этих 8 команд присвоен определенный код (от ООО до 111). Режимы адреса- ции (непосредственная, индексная через регистр X, индексная через регистр Y и др.) также имеют 3-разрядные коды. Пред- положим, что нам надо загрузить в регистр А код режима ад- О 1 Код Код режима Код мнемоники. адресации группы Рис; 52.1. ресации, используемый в команде с меткой L5. Для этого мож- но написать: LDA L5 ; Код операции в регистр А AND ф % ООО 11100 ; Замаскировать коды мнемоники и ; группы LSR ; Сдвинуть к правому LSR ; концу регистра А Сдвиг интересующего нас поля к правому концу регистра А осуществляется для того, чтобы число, содержащееся в этом поле (в данном случае от 0 до 7), имело вид числа, просто за- груженного в регистр (в данном случае от 00000000 до 00000111). Теперь для того, например, чтобы проверить, не ра- вен ли код способа адресации 5, можно использовать команду СМР ф!5 (без двух команд LSR было бы СМР ф!20). Сдвиг можно было осуществлять и перед маскированием: LDA L5 ; Код операции в регистр А LSR ; Сдвинуть к правому LSR ; концу регистра А AND % 00000111 ; Замаскировать все, кроме кода режима ; адресации Заметьте, что маску пришлось в этом случае изменить. Из этого примера видно, почему маска называется маской; все поля, кро- ме интересующего нас, очищаются или маскируются (скрыва- ются). Оба описанных способа использования команды AND можно объединить, если требуется проверить равенство нулю всего поля: 13»
196 Статья 52 LDA L5 ; Загрузить А из L5 и замаскировать все, AND #%00011100 ; кроме режима адресации. Флажок BNE NONZ ; нуля установлен, если код адресации ; равен 0 В этом примере осуществляется переход на метку NONZ, если код режима адресации команды по адресу L5 не равен 0. Команда AND устанавливает не только флажок нуля, но и флажок знака. Заметьте, однако, что команда AND =44= %01111111 не позволяет получить абсолютное значение цело- го со знаком; если число отрицательно, оно должно быть преоб- разовано в форму дополнения до двух. Пример расположения полей кодов мнемоники и режима ад- ресации, данный на рис. 52.1, взят из табл. П15. Все коды опе- раций процессора 6502 попадают в ту или иную «группу». Упражнения 1. Найдите логическое И приведенных ниже пар 2-разряд- ных шестнадцатеричных чисел, преобразовав сначала числа в двоичную форму, получив логическое И и преобразовав затем результат назад в шестнадцатеричную форму: %, а) 20 и 35; б) F6 и С7; в) 55 и АА. 2. Найдите логическое И приведенных ниже пар 2-разряд- ных шестнадцатеричных чисел. Попытайтесь выполнить двоич- ное преобразование в уме (результат представьте в шестнадца- теричной форме): а) 35 и 0F; * б) 72 и F0; в) 94 и 81. >(< 3. Напишите программу загрузки регистра А кодом мнемо- ники из ячейки L5 (левые 3 разряда) путем сдвига влево (с по- мощью ROL) и маскирования. (Вам придется пойти на неболь- шую хитрость с учетом того факта, что команда ROL в действи- тельности сдвигает 9-разрядный регистр, как это было показа- но в статье 34).
Логическое ИЛИ 197 Статья 53 Логическое ИЛИ Команда ORA (логическое ИЛИ с содержимым регистра А) предназначена для установки в 1 (а не в 0) отдельных битов содержимого регистра А (вы, несомненно, заметили, что мнемо- ники процессора 6502 всегда содержат 3 буквы; поэтому к сло- ву OR в конце добавлена буква А, обозначающая регистр А). В этом случае биты маски, установленные в 1 (а не в 0), указывают, какие биты операнда устанавливаются в 1. Так, ко- манды ORA # % 10000001 или ORA #$81 устанавливают в 1 первый и последний биты содержимого ре- гистра А. Как и AND, команда ORA используется главным об- разом с двоичной или шестнадцатеричной константой (маской). Так же как и AND, команда ORA является командой поби- товой обработки. Пользуясь обозначениями предыдущей статьи, дадим определение Rk через Рк и Qk для команды ORA: Qk = 0 Qk = 1 Pk = 0|Rk = 0 Rk = О Pk = 1 Rk = 0 Rk = 1 ujw- кратко 0 1 o[o 0 1 0 I В математической логике правая таблица представляет собой таблицу истинности для логического ИЛИ, описывая выраже- ние R=P ИЛИ Q, потому что Р истинно ( = 1), если либо Р, либо Q (либо оба вместе) истинны. В статье 24 указывалось, что значение самого левого разря- да кода любого символа, выводимого на экран дисплея (с по- мощью подпрограммы COUT), всегда равно 1. Команда ORA часто используется (например, см. статью 73) для установки этого разряда в 1 перед выводом символа на экран, если нет уверенности, что в нем 1. Аналогично тому как команда AND используется для рас- паковки байтов, с помощью команды ORA можно упаковать по- ля в байте. Такое использование команды ORA основывается на том, что логическое ИЛИ любой величины Q и нуля дает самое величину Q. Предположим, что у нас есть 3 байта с наз- ваниями MCODE, AMODE и FCODE, содержащие коды мнемо- ники, режима адресации и группы соответственно, как и в пре-
198 Статья 53 дыдущей статье. Мы можем объединить эти коды в одном бай- те с названием OPCODE следующим образом: LDA MCODE ; Загрузить код мнемоники в регистр А ASL ; Сдвинуть этот код ASL ; на 3 разряда ASL ; влево ORA AMODE ; Включить в байт код режима ; адресации ASL ; Сдвинуть оба кода влево ASL ; еще на 2 разряда ORA FCODE ; Включить в байт код группы ORA OPCODE; Результат — код операции Вместо команды ORA можно было бы воспользоваться коман- дой сложения; однако в этом случае пришлось бы сбрасывать флажок переноса. Команда ORA широко используется в тех случаях, когда мы по отдельности обрабатываем две 4-разряд- ные половины байта (см. статью 83). Команда ORA, так же как и AND, устанавливает флажок знака. Заметьте, что команда ORA * % 10000000 не преобразует положительное число в равное ему по абсолютной величине от- рицательное; для этого надо образовать его дополнение до двух. Команда ORA устанавливает также и флажок нуля. Если в качестве маски используется константа, флажок нуля всегда сбрасывается, указывая на ненулевой результат, если только мас- ка не равна 0. Можно заметить, что команда OR А #$0 не из- меняет содержимого регистра А; ее можно использовать для ус- тановки флажков нуля и знака в соответствии с текущим со- держимым регистра А (если указанные флажки изменили свое состояние в результате выполнения предыдущей команды, не использующей регистр А, например INX, DEX, ASL Q и др.). Заметьте также, что команда AND #$FF делает то же самое. То обстоятельство, что команда ORA устанавливает флажок нуля, можно использовать для проверки 16-разрядной величи- ны на 0. Так, в строках LDA Р ORA P4-I1 BEQ PZERO осуществляется переход на метку PZERO, если 16-разрядная величина Р равна 0, потому что в этом и только в этом случае логическое ИЛИ с его обоими байтами даст 0. Этот же прием можно использовать с числами, содержащими 24 разряда, 32 разряда и т. д.
Логическое ИСКЛЮЧАЮЩЕЕИЛИ 199 Упражнения 1. Найдите логическое ИЛИ приведенных ниже пар чисел, преобразовав их из шестнадцатеричной формы в двоичную, вы- полнив логическое ИЛИ и преобразовав затем результат на- зад в шестнадцатеричную форму: а) 20 и 35; * б) F6 и С7; в) 55 и АА. 2. Найдите логическое ИЛИ приведенных ниже пар шест- надцатеричных чисел. Попытайтесь выполнить -двоичные пре- образования в уме: а) 35 и 0F; б) 72 и F0; >| ч в) 04 и 81. 3. Напишите программу формирования в памяти байта Р из двух шестнадцатеричных цифр, LDIGIT (левой) и RDIGIT (правой). Считайте, что LDIGIT и RDIGIT представляют со- бой 1-байтовые величины со значениями в диапазоне от 0 до 15, Статья 54 Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ Наша третья и последняя команда побитовой обработки дан- ных EOR (Exclusive OR — ИСКЛЮЧАЮЩЕЕ ИЛИ) предназ- начена для изменения отдельных битов содержимого регистра А, т. е. для установки их в 1, если они имели значение 0, и для установки их в 0, если они имели значение 1. Как и команды AND и ORA, EOR обычно используется с двоичной или шест- надцатеричной константой — маской. Изменяемые биты опреде- ляются битами маски, установленными в 1. В этом отношении EOR напоминает ORA. Так, команды EOR #%00001111 или EOR #$0F
200 Статья 54 изменяют правые 4 бита содержимого регистра А, оставляя ле- вые 4 бита без изменения. В математике выражение Р ИЛИ Q включает в себя слу- чай, когда и Р и Q вместе истинны (как в предложении «ай=0, если и только если а=0 или Ь=0»), В разговорном языке «Р или Q» исключает этот случай (как в предложении «или Смит, или Джонз победит на выборах»). Это так называемое ИС- КЛЮЧАЮЩЕЕ ИЛИ иногда используется в математической логике, и оно же нашло выражение в команде EOR, для которой определение Rk через Рк и Qk (обозначения те же, что и в пре- дыдущих статьях), следующее: Ок = 0 Ок = 1 0 1 Рк = 0 Rk = 0 Rk = 1 или кратко 0 0 1 Рк = 1 Rk = 1 Rk = 0 110 Легко заметить, что это и есть таблица истинности для ИС- КЛЮЧАЮЩЕГО ИЛИ (данная таблица совпадает с таблицей истинности для обычного или ВКЛЮЧАЮЩЕГО ИЛИ с той раз- ницей, что если Рк и Qk равны 1, Rk равно 0; другими словами, исключается случай, когда обе величины истинны). ИСКЛЮ- ЧАЮЩЕЕ ИЛИ любой величины Q и нуля дает самое величи- ну Q, как это было и для обычного ИЛИ. Команду EOR можно использовать для выполнения знако- вых сравнений. Команда EOR #% 10000000 (или EOR #$80) преобразует величину р со знаком ( — 128^/^127) в величину р+128 без знака (0^р+128^255)4 Каждому числу в знако- вом диапазоне соответствует, в этом смысле, число в беззнако- вом диапазоне. Далее, P<Q (Р й Q — числа со знаком), если и только если P+128<Q+128 (Р+128 и Q+128 — числа без знака). Поэтому в программе LDA Q ; Загрузить Q (со знаком) EOR #% 10000000 ; Преобразовать в Q+128 (без знака) STA TEMP ; Сохранить Q+128 LDA Р ; Загрузить Р (со знаком) EOR #% 10000000 ; Преобразовать в Р+128 (без знака) СМР TEMP ; Сравнить с Q+128 ВСС LESS ; Если P+128<Q+128, то P<Q происходит переход на метку LESS, если P<Q, при этом и Р и Q — числа со знаком (другой способ знакового сравнения рас- смотрен в статье 56). D Вы можете убедиться, выполнив несколько примеров, что прибавление числа 128 (двоичное 10000000) к любому числу со знаком эквивалентно из- менению его самого левого бита (на 1, если был 0, и на 0, если была 1).
Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ 201 Конечно, команда EOR # % 10000000 не преобразует поло- жительное число в равное ему по абсолютной величине отрица- тельное. Однако мы можем использовать команду EOR 11111111 (или EOR#$FF) для преобразования числа в его дополнение до единицы. Вспомнив (см. статью 7), что до- полнение до двух — это дополнение до единицы плюс 1, мы по- лучаем быстрый способ вычитания содержимого регистра X из постоянной k. Для этого надо образовать дополнение до едини- цы величины X—(£+1). Действительно, обозначив результат z, получим: z-j-l——(X— (&-|-1)), откуда z—k—X. Если k=l, мы можем написать ТХА SEC SBC #!8 EOR #$FF ; Вычислить X—8, затем получить ; дополнение до 1. Обозначив результат ; z, имеем: z-|-l =—(X—8)—8—X, ; или z= (8—X) — 1 =7—X Команда EOR, выполненная дважды, сама себя аннулирует: если за EOR п следует другая команда EOR п, то содержимое регистра А не изменяется (при любом п). В частности, EOR *% 10000000 преобразует р-|-128 (без знака) обратно в р (со знаком), и наоборот. Это свойство команды EOR можно исполь- зовать для замещения полей-, если, например, поле режима ад- ресации ячейки OPCODE надо заменить на поле режима адре- сации из ОР2, можно написать LDA OP2 ; ; Загрузить код адресации из ОР2 AND #%00011100 ; Замаскировать все остальные поля STA TEMP ; Сохранить результат LDA OPCODE ; Загрузить OPCODE и замаскировать AND #% 11100011 ; старое значение кода адресации ORA TEMP ; ; Включить новое значение кода ; адресации STA OPCODE ; Записать новый код операции Однако можно с помощью необычного приема укоротить про- грамму на 2 команды: LDA OPCODE . EOROP2 AND 11100011 EOR ОР2 ; Загрузить старый код операции ; Поле кода адресации очищено и ; заменено новым кодом командой EOR. ; На остальные поля EOR воздействует ; дважды, поля не изменяются STA OPCODE ; Записать новый код операции
202 Статья 55 В некоторых случаях можно использовать как EOR, так и ORA. В частности, в примерах этой и предыдущей статей ко- манды ORA, сопровождаемые комментарием «Включить», мож- но было заменить на EOR. На некоторых других ЭВМ. команда ИСКЛЮЧАЮЩЕЕ ИЛИ имеет мнемонику XOR. Ассемблер LISA позволяет исполь- зовать, при желании; мнемонику XOR вместо EOR. Упражнения ' 1. Найдите логическое ИСКЛЮЧАЮЩЕЕ ИЛИ приведен- ных ниже пар чисел, преобразовав их из шестнадцатеричной формы в двоичную, выполнив логическое ИСКЛЮЧАЮЩЕЕ ИЛИ и преобразовав результат назад в шестнадцатеричную форму: 1 а) 20 и 35; б) F6 и. С7; в) 55 и АА. j 2. Найдите логическое ИСКЛЮЧАЮЩЕЕ ИЛИ приведен- ' ных ниже пар чисел. Попытайтесь выполнить двоичные преобра- зования в уме: ( а) 35 и OF; I * б) 72 и F0; в) 94 и 81. 3. Напишите программу перехода на метку LEQ, если чис- | ло Р (со знаком) меньше или равно числу Q (со знаком). Ва- ша программа должна содержать 7 команд. (Подсказка: см. статью 28). Статья 55 Проверка битов Мы уже видели, как с помощью команды AND можно про- верять отдельные биты байта. В процессоре 6502 предусмотре- на еще одна команда’), выполняющая примерно то же самое — Относительно немногие процессоры имеют команду типа BIT. Однако почти во всех процессорах имеются аналоги команд AND, ORA и EOR.
Проверка битов 203 это команда BIT (Bit test — проверка битов). Команда BIT Q выполняет операцию логического ИЛИ над содержимым ячей* ки Q и регистра А и устанавливает флажок нуля так же, как это сделала бы команда AND Q. Поэтому последовательности команд одинаково выполняют переход на метку BZERO, если четвертый справа бит Q установлен в 0. LDA Q LDA #% 00001000 AND #%00001000 BIT Q BEQ BZERO BEQ BZERO Отличия команд AND и BIT заключаются в следующем: 1. Команду BIT нельзя использовать с индексной адреса- цией. 2. Команду BIT нельзя использовать с константой (напри- мер, BIT #$80). 3. Команда BIT не изменяет содержимого регистра А. Это свойство команды BIT можно использовать для проверки содер- жимого ячейки Q с помощью «скользящей маски», которая в исходном состоянии имеет значение 00000001, и с каждой про- веркой сдвигается на 1 разряд влево. Так, приведенная ниже программа представляет собой другой возможный способ под- счета установленных разрядов в ячейке Q (см. статью 35): LDY #!0 ; LDA #!1 ; Начальное значение счетчика битов Начальное значение маски TALLY BIT Q ; Проверить этот бит (не менять маску) BEQ TALLY1 ; Если установлен в 0, прибавить 1 INY ; в счетчик битов TALLY1 ASL Сдвинуть маску и повторить, если BCC TALLY ; маска не сдвинулась до разряда переноса Если мы добавим команду т LDA Q к тексту первой программы из статьи 35 (чтобы получить две эквивалентные программы), то последняя программа окажется на 16 машинных циклов мед- леннее, но на 1 бант короче. Команду BIT можно использовать для проверки с помощью одной и той же маски содержимого нескольких ячеек: LDA #$1 ; Установить маску BIT Cl ; Проверить правый BNE Cl ODD ; бит Cl BIT C2 ; Проверить правый BNE C2ODD ; бит С2
204 Статья 55 В программе осуществляется переход на метку C1ODD, если содержимое С1 нечетно (правый бит установлен в 1); в против- ном случае происходит переход на метку C2ODD, если нечет- но содержимое С2. Естественно, с помощью той же маски мож- но было бы продолжить проверку ячеек. 4. Команда BIT Q устанавливает флажок знака в соответст- вии со знаком Q и независимо от знака содержимого регистра А. Это делает команду BIT единственной командой процессора 6502, которая устанавливает флажки нуля и знака по резуль- татам выполнения двух разных вычислений (вспомним, что ус- тановка флажка нуля производится по результатам операции И над содержимым ячейки Q и регистра А). Это свойство ко- манды BIT позволяет предложить еще один вариант перехода на метку QNEG, если Q отрицательно (см. статью 28): BIT Q ; Проверить знаковый BMI QNEG ; разряд Q Заметьте, что эти команды не изменяют содержимого регистров А, X и Y, следовательно, их можно применять, даже если все эти регистры используются (или их содержимое неизвестно). Приведенная последовательность команд эквивалентна вариан- там из статьи 28 и по времени, и по занимаемой памяти. Еще одно свойство команды BIT будет рассмотрено в конце следующей статьи. Упражнения 1. Команда BIT Q аналогична команде AND Q за тем ис- ключением, что AND Q засылает результат в регистр A, a BIT Q не делает этого. Какая пара команд процессора 6502 харак- терна тем же? 2. Что неверно в приведенной ниже программе подсчета чис- ла отрицательных элементов массива Т от Т(1) до T(N)? LDX N LDY #!0 LOOP BIT T-Il, X BPL EPOS INY EPOS DEX BNE LOOP 3. Какую экономию памяти и времени дает программа (рас- смотренная в тексте статьи)
Флажок переполнения 205 LDA BIT Cl BNE CIODD BIT C2 BNE C2ODD по сравнению с ее вариантом с командой AND? Заметьте, что время экономится только в том случае, если не происходит пе- реход на метку C1ODD. Статья 56 Флажок переполнения Если мы складываем два числа без знака и результат слиш- ком велик, чтобы поместиться в 1 байт (т. е. больше 255), ус- танавливается флажок переноса. Аналогично, если два числа без знака участвуют в операции вычитания и результат оказы- вается меньше 0, это фиксируется флажком переноса (который сбрасывается; в противном случае он был бы установлен). Если, однако, в операциях сложения или вычитания участ- вуют два числа со знаком, результат также может быть слиш- ком большим, или слишком маленьким, чтобы поместиться в 1 байт как число со знаком. Это называется арифметическим пе- реполнением, или просто переполнением. Переполнение отличается от переноса. Например, 100+100= =200 (десятичные), и операция сложения 100+100 не дает пе- реноса, потому что 200 меньше 256; однако переполнение имеет место, так как диапазон чисел со знаком составляет от —128 до 127, и 200 больше 127. Или рассмотрим операцию (—3)+5. Это то же самое, что выполнить сложение чисел без знака 253+5, и, поскольку ре- зультат превышает 255, возникает перенос; однако переполне- ния не возникает, так как (—3)+5=2, и 2 вполне укладыва- ется в диапазон представления чисел со знаком (от —128 до 127). По этой причине для признака переполнения в процессо-
206 Статья 56 ре 6502 выделен специальный флажок. Он имеет обозначение V (от Overflow — переполнение; не О, потому что, например, 0 = 1 выглядит как «нуль равен единице»), и команды перехо- да по состоянию этого флажка (0 или 1) отражают это обозна- чение: BVC L Переход на L, если флажок переполнения сброшен BVS L Переход на L, если флажок переполнения установлен Этим заканчивается наше изучение команд условных переходов. Их всего 8, и они связаны с четырьмя флажками (нуля, знака, переноса и переполнения): BEQ, BNE, BPL, BMI, ВСС, BCS, BVC и BVS. Команды ADC и SBC устанавливают флажок переполнения, если имело место переполнение, и сбрасывают его, если перепол- нения не было. Так, программа LDA Р ; Сложить Р и Q и перейти CLC ; на метку OVER, если P+Q ADC Q ; вышло из диапазона чисел BVS OVER ; со знаком (от —128 до 127) осуществляет переход на метку OVER, если при сложении Р и Q возникает переполнение. Заметьте, что среди всех вычислительных команд, которые могут приводить к переполнению (например, команды инкре- мента, декремента и сдвига), только команды ADC и SBC влияют на состояние флажка переполнения. Особенно важно, что состояние флажка переполнения не зависит от результата выполнения команды сравнения. В табл. ПЗ показано, какие команды влияют на состояние флажка переполнения; они по- мечены буквой V в столбце «Флажки». Обсуждая сравнение чисел со знаком в статье 28, мы отме- чали невозможность использования команд BPL и BMI, так как если, например, Р=—29 и Q=100, тогда P<Q и разность Р—Q отрицательна (конкретно, —129), но знаковый разряд этого числа содержит нуль. С другой стороны, переполнение в этом случае возникает, так как —129 выходит из диапазона зна- комых чисел. Пусть теперь мы хотим перейти на метку LESS, если P<Q. Нетрудно видеть, что пока нет переполнения, можно пользо- ваться командой BMI. Далее, если переполнение имеет место, то возможны два случая: 1) результат больше 127. В этом случае число положитель- но, но его знак отрицателен; 2) результат меньше —128. В этом случае число отрица- тельно, но его знак положителен.
Флажок переполнения 207 Другими словами, знак получается неправильным в любом случае; поэтому мы можем воспользоваться командой BPL, а не BMI (BPL осуществляет переход и по нулю, но у нас нуль получиться не может). В результате мы имеем другой способ перехода на LESS по условию P<Q, где Р и Q — числа со знаком: LDA P ; Вычислить P —Q обычным образом SEC ; и установить флажки переноса SBC Q ; и переполнения BVS OVSET ; Переполнение произошло? BPL GEQ ; Нет, перейти на GEQ, если P>=Q BMI LESS ; и на LESS, если P<Q OVSET BPL LESS ; Да, проверка знака (неправильного) GEQ (следующая команда) Эта программа короче и быстрее предыдущего варианта в ста- тье 54. Точно так же как мы сбрасывали разряд переноса (командой CLC), мы можем сбросить разряд переполнения; это делает ко- манда CLV. Однако команда установки разряда переполнения не предусмотрена. Переполнение имеет любопытное теоретическое свойство. Ко- манда ADC складывает два двоичных числа точно так же, как мы это делали в статье 3 — справа налево. В каждом разряде может возникнуть перенос. В частности, в самом левом разряде можно рассматривать внешний перенос (который устанавлива- ет флажок переноса) и внутренний перенос (т. е. перенос из пре- дыдущего разряда, второго с левой стороны). Можно показать, что разряд переполнения устанавливается в соответствии с ре- зультатом логической операции ИСКЛЮЧАЮЩЕГО ИЛИ над внутренним и внешним переносом в этом разряде. Это обстоя- тельство помогает упростить аппаратуру процессора 6502. Последнее свойство команды BIT, упомянутое в предыдущей статье, заключается в том, что BIT Q помещает второй слева разряд байта Q в разряд переполнения (независимо от содер- жимого регистра А). Это позволяет нам быстро проверить с по- мощью команд BVC или BVS этот конкретный разряд байта, если, конечно, это нужно. Упражнения 1. >|с а) Сколько памяти экономится в программе знакового сравнения этой статьи по сравнению с вариантом из статьи 54? б) Сколько машинных циклов экономится в програм-
208 Статья 56 ме знакового сравнения этой статьи, когда происходит переход на метку GEQ, по сравнению с вариантом из статьи 54? Учти- те, что переход на GEQ может происходить в двух разных слу- чаях. Полное число машинных циклов в обоих случаях одина- ково. в) Сколько циклов (максимально и минимально) эко- номится в программе знакового сравнения этой статьи, когда происходит переход на метку LESS, по сравнению с вариантом из статьи 54? Учтите, что переход на LESS может происходить в двух разных случаях. Один из них требует больше машинных циклов, чем другой. 2. а) Измените программу этой статьи так, чтобы первые 3 команды остались те же, но в качестве четвертой использова- лась команда BVC OVCLR. Заметьте, что действие этой про- граммы после вычитания можно представить следующей таб- лицей: Флажок переполнения Сброшен Установлен Знак Переход на Переход на + GEQ LESS Знак Переход на Переход на — LESS GEQ Измените оставшуюся часть программы так, чтобы результат ее работы во всех четырех случаях остался тем же. б) Изменяется ли при этом размер программы в па- мяти? 3. а) Используя таблицу из упр. 2, перепишите програм- му этой статьи так, чтобы ее первые 3 команды остались те же, а в качестве четвертой использовалась команда BMI NEG. Из- мените оставшуюся часть программы так, чтобы результат ее работы во всех четырех случаях остался тем же. б) Изменяется ли при этом размер программы в па- мяти?
Сохранение и восстановление переменных 209 Статья 57 Сохранение и восстановление переменных В этой статье и последующих 7 статьях мы рассмотрим ряд фундаментальных моментов, связанных с использованием под- программ. При написании подпрограмм часто совершают одну и ту же ошибку, о которой было упомянуто в статьях 25, 34 и 42 и ко- торую теперь мы рассмотрим подробно. Эту ошибку можно про- иллюстрировать следующим образом. Пусть у нас есть цикл, в котором N раз вызывается подпрограмма SUB: LDX N LOOP JSR SUB DEX BNE LOOP ; Счетчик в регистре X ; Вызов SUB ; Декремент счетчика ; Если не 0, повторить Давайте внимательно рассмотрим этот цикл. Сначала мы загру- жаем N; затем вызываем подпрограмму SUB; наконец, умень- шаем N на 1 или по крайней мере думаем, что делаем это. Во- прос в следующем: после выхода из подпрограммы, когда мы выполняем команду DEX, откуда мы знаем, что N все еще в ре- гистре X? Если подпрограмма SUB проста, она совсем не использует регистр X и проблема отпадает; но может быть и так, что в под- программе SUB есть собственный цикл, и тогда она, завершив- шись, оставляет в регистре X нуль. В этом случае команда DEX изменит его на —1, и команда BNE всегда будет осуществлять переход. Другими словами, мы получим бесконечный цикл. Про- блемы такого рода возникают, если подпрограмма SUB сама использует регистр X в вычислениях; не обязательно при этом образуется бесконечный цикл, но программа скорее всего будет работать неправильно. Та же неприятность может произойти, конечно, и с регистром Y; и она действительно происходит при использовании многих стандартных подпрограмм, например RDKEY (как было отмечено в статье 25). Как решить эту проблему? Один из возможных способов был предложен в статье 42. В начале подпрограммы SUB мы сохраняем содержимое регистра X (например, командой STX TEMP). В конце подпрограммы, перед самым выходом из нее, мы восстанавливаем содержимое регистра X (например, коман- дой LDX TEMP). Какое бы число ни содержалось в регистре X 14-589
210 Статья 57 к началу выполнения SUB, это число будет восстановлено в конце, так что будет казаться, что подпрограмма SUB совсем не изменила содержимого регистра X (другой способ сохране- ния и восстановления описан в статьях 60 и 61). В любой подпрограмме мы должны рассмотреть возмож- ность сохранения и восстановления содержимого различных ре- гистров. В этом нет необходимости в следующих случаях: 1) если некоторый регистр совсем не используется в под- программе (например, подпрограмма MULT из статьи 39 не ис- пользует регистр Y); 2) если подпрограмма возвращает данные (т. е. завершает- ся с данными в регистре). Например, при входе в подпрограмму MULT две перемножаемые величины должны быть в регистрах А и X, а при выходе из нее в регистрах А и X оказывается их произведение. В этом случае начальное содержимое регистров А и X не должно восстанавливаться; 3) если вызывающая программа предполагает, что содержи- мое данного регистра уничтожается подпрограммой. Так очень часто происходит при вызове подпрограммы RDKEY: если этот вызов находится в цикле, параметр цикла i хранится в памяти и модифицируется там же (командой DEC i). На некоторых ЭВМ (это не относится к процессору 6502) любая подпрограмма обязательно сохраняет и восстанавлива- ет содержимое всех регистров. Заметьте, однако, что в этом слу- чае регистры нельзя использовать для возврата данных. Сохранение и восстановление часто выполняется в середине программы. Может оказаться, что в какой-то точке программы требуется регистр, но все рабочие регистры (а их всего три) уже заняты. Если вам нужен регистр X, но он уже занят в про- грамме, сохраните его содержимое командой STX TEMP, ис- пользуйте регистр, как вам надо, а затем восстановите исходное содержимое командой LDX TEMP. Мы, конечно, не должны за- бывать и об альтернативах использования регистров. Можно, например, хранить параметр цикла в памяти (как в п. 3); сдвиг битов можно осуществлять не в регистре А, а в памяти. Если имеется свободный регистр, его можно использовать для сохранения и восстановления. Пусть нам надо выполнить некоторые вычисления в регистре А, но в нем хранится что-то, что понадобится позже. Если регистр X свободен, мы можем сохранить в нем содержимое регистра А командой ТАХ, а поз- же восстановить его командой ТХА. Это самый быстрый способ сохранения и восстановления (см., например, последнюю про- грамму в статье 34). Мы говорим, что подпрограмма обеспечивает сохранность регистра (preserves a register), если его содержимое в конце выполнения подпрограммы оказывается тем же, что и в начале.
Сохранение и восстановление переменных 211 Так может получиться потому, что содержимое регистра было сохранено и восстановлено, либо потому, что подпрограмма про- сто не использовала этот регистр. Примерами подпрограмм, не обеспечивающих сохранность регистров, являются GETLNZ, статья 25 (и GETLN из упражнений той же статьи), а также программы входного и выходного преобразования DECI, DECO и DECOZ, описанные в статье 41. Упражнения 1. Рассмотрите подпрограмму DIV из статьи 40. Должна ли эта подпрограмма сохранять и восстанавливать содержимое ре- гистров А, X и Y? Почему должна или почему не должна? >(< 2. Мы уже видели (см. статью 39), что команда RTS языка ассемблера напоминает предложение RETURN Бейсика или Фортрана. В программах на Бейсике или Фортране может быть несколько предложений RETURN. Аналогично и в программе на языке ассемблера может быть несколько команд RTS; одна- ко это маловероятно, если надо сохранять и восстанавливать содержимое регистров А, X и Y. Почему? (Подсказка-, подумай- те, сколько памяти потребуется для сохранения.) 3. Рассмотрите следующую подпрограмму: CALC BIT Q BPL CALCX STA TEMP LDA #!0 SEC SBC Q STA Q LDA TEMP CALCX RTS а) Подпрограмма CALC присваивает переменной Q значе- ние f(Q), где f — широко известная функция. Какая? б) обеспечивает ли подпрограмма CALC сохранность регист- ров А, X и Y? Зависит ли это от знака содержимого регистра А? Почему зависит или почему не зависит? 14*
212 Статья 58 Статья 58 Адреса возврата и косвенные переходы Мы уже сталкивались в статьях 25 и 39 с командами JSR (Jump to subroutine—переход на подпрограмму) и RTS (Re- turn from subroutine — возврат из подпрограммы). Теперь пора рассмотреть, как эти команды выполняются. Нам для этого по- надобится важное понятие адреса возврата. Рис. 58.1. Многократный вызов подпрограммы. Рассмотрим рис. 58.1. На нем изображена программа Р1, ко- торая трижды из разных точек обращается к подпрограмме Р2. (Программа Р1 может быть основной программой, а может быть и подпрограммой; мы называем ее вызывающей програм- мой, т. е. программой, которая вызывает подпрограмму Р2.) Предложение вызова, или перехода на подпрограмму (JSR Р2), очевидно, выполняет переход на Р2. Предложение возврата (RTS) также выполняет переход; но куда? При первом обра- щении к Р2 предложение возврата вызовет переход в точку а. Во второй раз будет переход в точку р, в третий раз — в точку у. Как же одна и та же команда может вызывать переход в три разные точки? Ответ заключается в механизме работы ко- манды перехода на подпрограмму. Эта команда не только вы- полняет переход, но также вычисляет и сохраняет в памяти ад- рес возврата, который представляет собой адрес команды, еле-
Адреса возврата и косвенные переходы 213 дующей за JSR. При первом вызове Р2 адресом возврата яв- ляется а. При втором вызове — 0; при третьем — у. Команда возврата теперь может выполнить переход по адре- су возврата, поскольку он хранится в памяти. Поэтому первый раз команда RTS вызовет переход на а; второй раз — на 0; третий раз — на у, как и требуется. Следующий вопрос заключается в том, где хранить адрес возврата. В разных ЭВМ этот вопрос решается по-разному. В некоторых ЭВМ, например IBM серии 4300 (или более старых ЭВМ IBM 360 и 370), адрес возврата хранится не в па- мяти, а в регистре. Команда возврата выполняет переход по ад- ресу, который находится в этом регистре. Это индексированный переход; он выполняется так, как если бы в составе команд процессора 6502 наряду с командой LDA Т, X была еще и ко- манда JMP Т, X (правда, в этом случае Т равнялось бы 0; мы сложили бы 0 с содержимым регистра X и выполнили переход по результирующему адресу). В процессоре 6502 такой команды не предусмотрено отчас- ти из-за того, что регистры X и У имеют длину лишь по 8 раз- рядов. Во многих других ЭВМ, однако, такие команды исполь- зуются. В некоторых старых ЭВМ, например CDC серии 6000 (раз- работка Control Data Corporation), адрес возврата из подпро- граммы Р2 записывается в начало Р2. Команда возврата из Р2 выполняет в этом случае переход по адресу, записанному в начале Р2. Любопытно отметить, что в процессоре 6502 предусмотрена команда перехода по адресу, записанному в некоторой ячейке Р2. Это команда JMP (Р2) (скобки здесь должны присутство- вать). Команда реализует косвенный переход; здесь использу- ется косвенная адресация (которая будет подробнее рассмотре- на в статьях 75 и 76). Адрес должен быть записан в ячейках Р2 и P2-f-!l с переставленными, как обычно, байтами. Предположим на время, что мы хотим использовать эту ко- манду для завершения подпрограммы. При вызове подпрограм- мы мы должны записать адрес возврата по адресу Р2. Пусть адресом возврата является CONT; тогда мы должны загрузить младшее полуслово CONT и записать его по адресу Р2, а затем загрузить старшее полуслово CONT и записать его по адресу Р2+П. Используя еще одну возможность ассемблера LISA, это можно сделать следующим образом: LDA #CONT ; Переслать адрес CONT STA Р2 ; в Р2 (младшее полуслово) LDA /CONT ; Переслать адрес CONT STA Р2+!1 ; в Р2 (старшее полуслово)
214 Статья 58 В командах, адресующихся к константе, для любого адреса (или другой 16-разрядной константы) J, обозначает его правое полуслово, a /J — левое. Конечно, если J представляет собой 8-разрядную константу без знака, ее можно рассматривать как 16-разрядную константу, у которой левое полуслово равно О, а правое представляет собой самое величину J, и мы по-преж- нему можем использовать обозначение #J. Заметьте, что если в команде используются операнды 4М и /J, то эта команда ста- новится 2-байтовой (с непосредственной адресацией). Однако команда RTS действует иначе. В действительности команда JSR записывает адрес возврата в некоторую структуру данных, называемую стеком; команда RTS извлекает адрес воз- врата из стека и производит переход. Такой способ хранения ад- ресов возврата является прогрессивной особенностью процессо- ра 6502 (как и ряда других микропроцессоров) и приводит, как мы увидим, к значительной экономии времени и памяти. Чтобы понять, почему это так, мы должны рассмотреть, что представ- ляют собой стеки. Упражнения 1. Предположим, что в процессоре 6502 не предусмотрены команды JSR и RTS, и вместо вычисления адреса возврата и за- писи его в Р2, как предложено в тексте, мы вызываем подпро- грамму Р2, поместив в регистр А код (1, 2, 3 и т. д.), указываю- щий, откуда был сделан вызов. Какие действия надо теперь вы- полнить в подпрограмме, чтобы обеспечить нормальный воз- врат? Напишите последовательность команд, которую можно было бы использовать в этом случае, предположив, что имеют- ся 4 адреса возврата ALPHA, BETA, GAMMA и DELTA и 4 кода 1, 2, 3 и 4. (Имейте в виду, что регистр А, возможно, бу- дет использоваться в подпрограмме для каких-то вычислений). 2. В языке Фортран имеется предложение GO ТО (Пь п2, ns ...), v, которое вызывает переход на метку если и = 1; на м2, если v—2, и т. д. Предположите, что у вас имеется таблица адресов «1, й2 и т. д., организованная в виде последовательного массива JTABLE 2-байтовых величин. Напишите последовательность команд процессора 6502, равнозначную приведенному выше предложению GO ТО, считая, что и находится в регистре А1). >) Пусть, например, в регистре А содержится 2. Вы, следовательно, хоти- те перейти на п2. Адрес «г будет вторым адресом в массиве JTABLE, и ваша программа должна извлечь этот адрес, переслать его в какую-то другую ячейку (оба байта адреса) и затем произвести косвенный переход через эту ячейку (назовем ее, например, IA). Произойдет переход на п2, так как кос- венный переход через ячейку IA происходит по адресу, содержащемуся в этой ячейке.
Стеки 215 (Считайте при этом, что v не может быть ни слишком большим, ни отрицательным. Внимательно отнеситесь к определению сме- щения, повторите материал статьи 32.) 3. Представьте, что вы пишете программу вычисления М= = 150OO/N с использованием подпрограммы DIV из статьи 40. Как можно облегчить процесс написания этой программы, ис- пользуя оператор / ассемблер LISA? Статья 59 Стеки Сначала мы познакомимся с общими характеристиками сте- ков, а затем рассмотрим аппаратный стек процессора 6502. В простейшем случае — а сложные случаи использования стека в этой книге рассматриваться не будут — стек представ- ляет собой массив переменной длины. Назовем наш стек Н, а его размер1* (длину)—X. Тогда можно считать, что массив Н состоит из элементов от Н(0) до Н(Х—1). Размер стека увеличивается, если Н(Х) присвоить значение некоторой величины А, а затем к X прибавить единицу. Такая операция называется проталкиванием А в стек (см. рис. 59.1). Мы можем уменьшить размер стека, вычтя 1 из X. При этом Н(Х) снова попадает в А. Это обычно называется выталкива- нием из стека. И проталкивание, и выталкивание можно выполнить на Бей- сике: Н(Х)=А Х=Х-1 Х=Х+1 А=Н(Х) Проталкивание А Выталкивание А или на языке ассемблера процессора 6502 с помощью регистров А и X: STA Н, X DEX INX LDA Н, X Проталкивание А Выталкивание А *> В данном случае имеется в виду текущий размер стека, который из- меняется по мере изъятия из стека элементов или помещения в него новых. Обычно размером стека называют полный объем выделяемой под стек памя- ти (то, что ниже называется «максимальным размером»). — Прим, перев.
216 Статья 59 Любой стек имеет определенный максимальный размер, ко- торый мы будем обозначать max, так что всегда Х<.тах. Для процессора 6502 обычно max=256, так что максимальным до- пустимым значением (без знака) содержимого регистра X при любых условиях является Х<256, или Х^255. А Рис. 59.1. Проталкивание и выталкивание. Теперь представим себе, что мы только что протолкнули ве- личины РЗ, Р2 и Р1 (именно в этом порядке) в стек. Мы гово- рим, что Р1 находится на верхушке стека; Р2 представляет со- бой второй элемент стека (считая сверху вниз)-, РЗ — третий элемент сверху. Используя известные нам смещения, мы можем заметить, что
Стеки 217 LDA Н —!1, X LDA Н—12, X LDA Н — !3, X загружает Pl (верхушку стека); загружает Р2 (второй элемент сверху); загружает РЗ (третий элемент сверху). и вообще, LDA Н —In, X загружает n-й элемент сверху. Наличие в приведенных командах знака минус наводит на мысль о перевернутом стеке. В перевернутом стеке Н байты имеют обозначение не Н(0), Н(1), Н(2) и т. д., a H(max— 1), Н (ягах—2), Н(тах—3) и т. д. вниз до Н(Х+1), причем Н(Х) по-прежнему представляет собой элемент над верхушкой стека. Теперь X уже не характеризует размер стека. Размер стека ра- вен z, причем max—г=Х+1 (или z—max— (Х+1)=пгах— 1 — — X). Если пгах=256, то z=255 —X; другими словами, текущий размер стека представляет собой дополнение до единицы содер- жимого регистра X (см. статью 7). Проталкивание и выталкивание в таком стеке производится следующим образом: STA Н, X INX DEX LDA Н, X Проталкивание А Выталкивание А и если РЗ, Р2 и Р1 загружены в такой стек, то LDA Н+11, X LDA Н+12, X LDA НН-13, X загружает Р1 (верхушку стека); загружает второй элемент сверху; загружает третий элемент сверху и вообще, LDA H-]-!n, X загружает n-й элемент сверху, и мы за- менили знаки минус на знаки плюс. Стек можно использовать для сохранения и восстановления. Если мы сохранили несколько величин, протолкнув их в стек, то позже мы можем восстановить их, вытолкнув из стека. Заметьте, что при выталкивании элемента стека нет необхо- димости очищать верхушку стека (или присваивать верхнему элементу какое-либо иное определенное значение). После вы- талкивания этот элемент окажется над верхушкой стека, и его значение не будет играть роли. Позже стек может опять сде- латься больше; но когда это произойдет, этот элемент будет за- гружен новым значением. Упражнения 1. Измените приведенную в этой статье программу на Бей- сике (в которой величина А выталкивается из стека Н) так, чтобы в программе произошел переход на метку 799, если из стека уже нечего выталкивать, т. е. если стек пуст (в нем нет ии одного элемента). (Если, однако, стек становится пустым в
218 Статья 60 процессе выталкивания А, не переходите на 799). Следует ли проводить эту проверку перед двумя данными в тексте предло- жениями Бейсика, или после них? Почему? (Подсказка: вни- мательно проанализируйте, что происходит при Х=0.) 2. Измените приведенную в этой статье программу на Бей- сике (в которой величина А проталкивается в стек Н) так, что- бы в программе произошел переход на метку 899, если в стеке уже нет места для проталкиваемой величины, т. е. если стек полон (содержит максимально возможное количество элемен- тов). (Если, однако, стек заполнился в процессе проталкива- ния А, не переходите на 899). Считайте, что Н обозначается на Бейсике DIM Н(п), и что таким образом определяются эле- менты от Н(0) до Н(п). Следует ли проводить эту проверку пе- ред двумя данными в тексте предложениями Бейсика, или после них? Почему? (Подсказка-, внимательно рассмотрите, что происходит при Х=п.) 3. Предположите, что стек Н состоит из элементов от Н(1) до Н(Х), а не от Н(0) до Н(Х—1). Как это повлияет на пред- ложения Бейсика, служащие для проталкивания и выталкива- ния? Сформулируйте ответ простейшим образом. (Подсказка: что произойдет при изменении порядка этих двух предложений Бейсика?) Оставьте в стороне проверки на полный и пустой стек, проведенные в предыдущих упражнениях. Результат выполне- ния этого упражнения покажет вам, как чаще всего произво- дится загрузка и выгрузка стека в алгебраических языках типа Бейсика. Статья 60 Команды, ориентированные на использование стека Теперь мы можем рассмотреть до конца вопрос о механиз- ме выполнения команд JSR и RTS, а также ввести 4 новые ко- манды и рассказать еще об одном регистре процессора 6502. Аппаратный стек процессора 6502 является перевернутым (см. предыдущую статью); для него выделены фиксированные адреса с 0100 по 01FF. Стек связан со специальным регистром,
Команды, ориентированные на использование стека 219 называемым указателем стека. Это регистр S, информация о ко- тором появляется на экране в процессе пошагового выполнения, описанном в статье 47. Команда PHA (Push А — проталкивание А) проталкивает содержимое регистра А в аппаратный стек. Делается это сле- дующим образом: содержимое регистра А записывается по ад- ресу 0100+S, а затем содержимое регистра S уменьшается на 1. Именно такая процедура описывалась программой в кон- це предыдущей статьи, только вместо регистра X используется указатель стека S. Команда PLA (Pull А — выталкивание А) выталкивает со- держимое регистра А из аппаратного стека путем увеличения на 1 содержимого регистра S и загрузки регистра А из ячейки с адресом 01004-S. Это тоже совпадает с описанной ранее про- цедурой, в которой регистр X заменяется регистром S. Наконец, команда JSR проталкивает в аппаратный стек ад- рес возврата (минус 1 для упрощения аппаратных операций), а команда RTS выталкивает этот адрес из стека (и прибавляет к нему 1) и производит переход по этому адресу. Поскольку ад- реса представляют собой 16-разрядные величины, JSR протал- кивает 2 байта (сначала старший байт, чтобы адрес записался в памяти, как обычно, с переставленными байтами), a RTS вы- талкивает 2 байта1’. Команды РНА и PLA можно использовать для сохранения и восстановления содержимого регистра А. Содержимое регистров X или Y можно сохранить, предварительно переслав его в регистр А, и восстановить, переслав обратно. Если нужно сохранить со- держимое всех трех регистров А, X и Y, можно в начале подпро- граммы написать РНА ; Сохранить регистр А ТХА ; Переслать X в А РНА ; (это сохраняет регистр X) TYA ; Переслать Y в А РНА ; (это сохраняет регистр Y) U Пусть для примера в стеке уже записаны 3 байта. Они находятся в ячейках 01FF, 01FE и 01FD, а указатель стека содержит FC. Если в ячейках 08В4, 08В5 и 08В6 хранятся 3 байта команды JSR, следующая за JSR ко- манда начинается в ячейке 08В7, и 08В7 — это адрес возврата. После вы- полнения команды JSR в стеке будет 5 байт, в ячейках 01FF, 01FE, 01FD, 01FC и 01FB, а в указателе стека будет FA. 2 новых байта в стеке — это 08 (в ячейке 01FC) и В6 (в ячейке 01FB), потому что 08В6 — это адрес возврата (08В7) минус 1. После выполнения команды RTS число 08В6 вы- талкивается из стека, и производится переход по адресу 08В6+1, т. е. 08В7 '(адрес возврата); теперь в стеке, как и раньше, 3 байта, и в указателе сте- ка адрес FC (заметьте, что именно FC, а не 01FC — указатель стека 8-раз- рядный).
220 Статья 60 а в конце перед командой RTS написать PLA ; Восстановить регистр Y TAY ; Переслать назад в Y PLA ; Восстановить регистр X ТАХ ; Переслать назад в X PLA ; Восстановить регистр А Заметьте, что если мы сохраняем содержимое регистров А, X и Y в таком порядке, то содержимое регистра Y оказывается на верхушке стека. При восстановлении сначала надо восста- новить регистр Y, затем X и, наконец, А. В общем случае если мы сохраняем несколько величин проталкиванием в стек и вос- станавливаем выталкиванием, то выталкивать нужно в поряд- ке, обратном проталкиванию. Рис. 60.1. Два уровня вызовов подпрограмм.
Команды, ориентированные на использование стека 201 Команды вызова и возврата типа JSR и RTS, ориентирован- ные на работу со стеком, можно непосредственно использовать и в том случае, если имеются несколько уровней подпрограмм. Рассмотрим рис. 60.1, где программа Р1 вызывает подпрограм- му Р2, а Р2 вызывает подпрограмму РЗ. Последовательность событий здесь будет следующей: 1) Р1 вызывает Р2, и а (минус 1) проталкивается в стек; 2) Р2 вызывает РЗ, и 0 (минус 1) проталкивается в стек; 3) происходит возврат из РЗ в Р2. Адрес возврата выталки- вается из стека, причем это 0, потому что 0 сейчас находится на верхушке стека; 4) после возврата из РЗ продолжается выполнение програм- мы Р2, а затем происходит возврат в Р1. На этот раз из стека выталкивается адрес возврата а. Точно так же будут развиваться события и при наличии че- тырех или более подпрограмм, каждая из которых вызывает следующую. Заметьте, что адреса возврата выталкиваются в порядке, обратном тому, в котором они проталкивались, — а именно в этом порядке они и требуются. Команда TSX пересылает содержимое указателя стека в ре- гистр X. После того как это было сделано, команда LDA STACK+ll, X загружает верхушку стека; LDA STACK+I2, X загружает второй элемент сверху; LDA STACK+13, X загружает третий элемент сверху; и т. д., если в программе было определение STACK EQU $0100. Команда TXS пересылает содержимое регистра X в указа- тель стека. Это единственная возможность загрузки указателя стека: требуемая константа загружается в регистр X, и выпол- няется команда TXS. Однако пользователю вычислительной си- стемы типа Эпл не следует выполнять такую процедуру, так как система сама следит за состоянием указателя стека. Единствен- ным исключением из этого правила является декремент указа- теля стека без проталкивания чего-либо в стек. Для этого мож- но воспользоваться такой последовательностью команд: TSX ; Переслать указатель стека в X DEX ; Уменьшить его на 1 и TXS ; восстановить указатель стека Этим завершается наше изучение команд пересылки процессе ра 6502. Их всего 6: TAX, TAY, ТХА, TYA, TSX и TXS.
222 Статья 60 Упражнения 1. Содержимое ячеек с адресами от 01F0 до 01F7 указано в следующей таблице: Адрес Содержимое 01F0 82 01F1 1F 01F2 D6 01F3 4В 01F4 53 01F5 79 01F6 08 01F7 ЕА а) предположив, что указатель стека содержит F3 и выпол- нена команда JSR по адресу 08С1, определите новое содержи- мое указателя стека и ячеек 01F0 — 01F7; jfe б) предположив, что указатель стека содержит F4, и выпол- нена команда RTS, определите новое содержимое указателя стека; в) по какому адресу произойдет переход, если указатель стека содержит F4 и выполнена команда RTS? 2. Содержимое регистров А, X и Y равно соответственно 1, 2 и 3. Определите новое содержимое регистра А после выпол- нения приведенных ниже последовательностей команд: * а) ТХА; PHA; TYA; PLA; б) РНА; ТХА; PHA; PLA; PLA; * в) РНА; ТХА; PHA; TYA; PHA; PLA; PLA. ' 3. Программист решил сохранить и восстановить содержимое регистра X внутри подпрограммы, используя TXS и TSX. Мож- но ли так делать? Почему можно или почему нельзя? Задача 2 для решения на ЭВМ Проверка массива на повторяющиеся элементы Напишите программу ввода нескольких 8-разрядных вели- чин, представляющих собой десятичные числа, преобразования их с помощью программы DECI, заполнения ими массива и за- тем проверки, нет ли в массиве одинаковых величин. Последнее вводимое вами десятичное число должно сопро- вождаться двумя нажатиями клавиши возврата каретки. Таким образом, ваша программа сможет определить, что ввод закон- чен.
Использование аппаратного стека 223 Если две величины (например, п) в массиве одинаковы, ва- ша программа должна вывести сообщение «п IS DUPLICA- TED»; в противном случае выводится «NO DUPLICATIONS» (пусть только ваша программа не выводит кавычки). Заметьте, что нет необходимости искать последующие пары одинаковых величин, если одна такая пара уже найдена. В со- общении «п IS DUPLICATED» величина п представляет собой первую повторяющуюся величину, если их несколько. Статья 61 Использование аппаратного стека Использование подпрограмм является обычной практикой на микроЭВМ, даже еще более распространенной, чем на больших ЭВМ. Объясняется это нехваткой памяти. Вы должны взять се- бе за правило в программах на языке ассемблера для мик- роЭВМ не допускать повторения одинаковых последовательно- стей четырех и более команд; такую последовательность следу- ет оформить как подпрограмму и вызвать дважды (или столь- ко раз, сколько требуется). Кроме того, в программах для процессора 6502 приходится часто пользоваться сохранением и восстановлением, что объяс- няется малым количеством регистров (для сравнения можно указать, что в ЭВМ IBM 4300 только регистров общего назна- чения 16 штук, не говоря уже о регистре состояния, регистрах для вычислений с плавающей точкой и т. д.). Оба этих обстоя- тельства требуют умения использовать стек и регистр S. Кстати, не путайте регистр S с флажком S (признаком зна- ка). Изготовители процессора 6502, чтобы не было путаницы, называют флажок знака (S) флажком отрицательного резуль- тата (N) (от слова negative — отрицательный). Однако боль- шинство программистов предпочитают говорить «флажок зна- ка», так эта терминология используется в других микропроцес- сорах (например, Z80). В настоящей статье мы будем обозна- чать буквой S исключительно указатель стека.
224 Статья 61 Байты с адресами 0100, 0101 и т. д., до 0100+S, называются доступным стековым пространством. Эти байты можно исполь- зовать для сохранения в стеке новых величин. Байты с адреса- ми 0100+S4-1, 0100+S+2 и т. д., до 01FF, — это байты, кото- рые фактически находятся в стеке в данный момент. Верхушка стека имеет адрес 01004-S+1. При выходе из любой подпрограммы в регистре S должен находиться тот же адрес, что и при входе в эту подпрограмму. Байты, находящиеся в стеке при входе в подпрограмму, должны остаться неизменными при выходе из нее. Это два основных правила использования стека в подпрограммах. Если не использовать команды РНА, PLA и TXS, эти пра- вила соблюдаются автоматически. Команда JSR уменьшает S на 2; команда RTS увеличивает S на 2; поэтому пара команд JSR и RTS оставляет S неизменным. Далее, команда JSR запо- минает адрес возврата (минус 1), проталкивая его в стек, и это никак не влияет на байты, которые были загружены в стек перед выполнением команды JSR. Если команды РИА и PLA используются, то необходимо твердо помнить: при любых условиях все, что загружается в стек, должно быть из него выгружено. В противном случае бу- дут нарушены сформулированные выше правила (и в результа- те при выполнении команды RTS в конце подпрограммы вы получите неверный адрес возврата). Важнейшим применением команд РНА и PLA является со- хранение и восстановление. Фактически сохранение с помощью команды STA TEMP и восстановление командой LDA TEMP почти не практикуется. Вместо этого мы сохраняем командой РНА и восстанавливаем командой PLA. Заметьте, что это быст- рее на 1 машинный цикл и к тому же экономит 4 байта, потому что РНА и PLA — это 1-байтовые команды, в то время как STA TEMP и LDA TEMP — 3-байтовые. Комбинацию команд STA TEMP и СМР TEMP можно заме- нить командами РНА и СМР $0100, X, если перед этим была выполнена команда TSX, загружающая указатель стека в ре- гистр X. Позже, однако, вы должны выполнить команду PLA, даже если содержимое регистра А не будет использоваться в вашей программе (все, что было загружено, должно быть вы- гружено) . Тот же прием можно использовать для замены команд STA TEMP и ADC TEMP (или SBC TEMP и проч.). Это, однако, сплошь и рядом не стоит усилий, учитывая необходимость зани- мать регистр X (в статье 80 будет обсуждаться другая, более общая возможность замены команды STA TEMP). Другой весьма распространенный прием используется в тех случаях, когда подпрограмма (назовем ее Р) вызывает другую
Использование аппаратного стека 225 подпрограмму (назовем ее Q) и немедленно завершается^. Две команды JSR Q и RTS2* можно в этом случае заменить одной командой JMP Q (команда RTS, обеспечивающая возврат из под- программы Q, выполняет тогда возврат из подпрограммы Р). Еще один распространенный прием часто используется в тех случаях, когда подпрограмма «завершается в середине», т. е. когда последней командой подпрограммы является команда JMP перехода в некоторую точку внутри подпрограммы, орга- низующей внутренний цикл. Выход же из подпрограммы осу- ществляется путем условного перехода на команду RTS. В этом случае условный переход вполне может выполняться на коман- ду RTS другой подпрограммы, наша же подпрограмма вообще не требует команды RTS, что экономит 1 байт (примером этого служит команда BNE DONE в программе статьи 42). Переполнение стека — использование в стеке более 256 байт — очень редкая ситуация в процессоре 6502. Обычно мы не предусматриваем проверки на переполнение. Это не приводит к сбоям, поскольку стек даже при переполнении никогда не выйдет за пределы области адресов 0100 — 01FF. (Если вер- хушка стека находится в ячейке 0100 и байт проталкивается в эту ячейку, так что стек заполняется доверху, указатель стека изменяет свое содержимое с 00 на FF. Следующие байты бу- дут записаны в ячейках 01FF, 01FE и т. д.3)). Если вам все же хочется проверить стек на переполнение после проталкивания или выяснить, не стал ли он пуст после выталкивания, можно выполнить команды TSX и СРХ =H=$FF (заметьте, что указатель стека содержит шестнадцатеричное FF и в случае, если стек полон, и в случае, если он пуст). Не забывайте, что в подпрограмме стек никогда не может быть пуст, так как в нем по меньшей мере содержится адрес воз- врата. Упражнения 1. Приведите предложение Бейсика, соответствующее такой последовательности команд: LDA I РНА SEC SBC J *> Командой RTS выхода в основную программу (т. е. ту, из которой вызвана подпрограмма Р). — Прим, перев. 2) Стоящие в конце подпрограммы Р. — Прим, перев. 3) Тем самым будет потеряно содержимое этих ячеек, т. е. начало стека. Это почти неминуемо приведет к неправильному выполнению программы.— Прим, перев. 15—589
226 Статья 62 TAX PLA CLC ADC T, X STA К (здесь массив T начинается с Т(0)). 2. Приведите последовательность команд, не содержащую команды RTS, но эквивалентную ей (т. е. выполняющую опе- рации выталкивания из стека двух байт, прибавления 1 к по- лученному 16-разрядному адресу и перехода по этому адресу). 3. Как можно оптимизировать программу KSUBR JSR KINPUT JSR KCALC JSR KOUT RTS воспользовавшись приемом, приведенным в тексте? Статья 62 Программа входного преобразования В качестве реального примера использования стека мы де- тально рассмотрим программы входного и выходного преобра- зования, упомянутые в статье 41. Программа входного преобразования изображена на рис. 62.1. В ней используется следующая процедура (аналогич- ная описанной в статье 33): 1) очистить N; 2) ввести цифру D. Если следующий символ — не цифра, ос- тановиться. В N находится результат; 3) вычислить N=10;|<N4-D и вернуться к шагу 2.
Программа входного преобразования ; DECI — десятичный ввод (Decimal input) ; При входе индекс буфера INBUF в регистре X, ; т. е. команда LDA $0200, X загружает первый ; символ из входной строки ; При выходе перенос установлен в случае переполнения, ; т. е. если число больше, чем 65535 ; В противном случае число в регистрах А и X ; (старшее полуслово —в А, младшее полуслово —в X), ; а перенос сброшен DECI9 EQU $0200 Входной буфер DECI LDA *!0 Поместить число N в ячейку DECI6 STA DECI6 и в регистр А DECI1 TAY Теперь в DECI6 и Y DECIA LDA DECI9,X Получить символ CMP #"9"+!l Десятичная цифра? BCS DECI3 Нет, код слишком велик SBC #"0"-!l Преобразовать и проверить, BCC DECI3 не слишком ли мал INX Для следующего символа STA DECI7 Сохранить цифру D TYA N в DECI6 и А JSR DECI4 2N в DECI6 и А STA DECI8 Поместить 2N в Y LDY DECI6 и DECI8 JSR DECI4 4N в DECI6 и А JSR DECI4 8N в DECI6 и А ADC DECI8 Перенос сброшен при PHA выходе из DECI4 TYA Поместить 10N в А и на ADC DECI6 верхушку стека BCS DECIB Если переполнение, выйти STA DECI6 Поместить 10N в PLA DECI6 и А ADC DECI7 Вычислить 10N+D BCC DECI1 Если перенос установлен, INC DECI6 прибавить 1 к левому полуслову BNE DECI1 Если переполнение, выйти RTS с установленным переносом DECI3 TYA Символ — не цифра, следовательно, TAX число закончилось LDA DECI6 Поместить N в А и X CLC и выйти RTS со сброшенным переносом DECI4 ASL Двойной сдвиг ROL DECI6 (умножение на 2) BCC DECI5 Если переполнение, 15*
228 Статья 62 PLA Продолжение ; вытолкнуть 2 байта DECIB PLA ; адреса возврата DECI5 RTS ; Выход из программы DECI6 DFS II ; Временное хранение DECI7 DFS И ; Цифра D DECI8 DFS II ; Временное хранение Рис. 62.1. Программа входного преобразования. Пусть, к примеру, вводится строка «125-J-». Тогда последова- тельные значения N будут: 1) 0; 2) 10*0 +1, или 1; 3) 10*1 4-2, или 12; 4) 10*124-5, или 125. Умножение на 10 выполняется, как было описано в статье 33, но умножаются 16-разрядные величины. Поэтому 16-разряд- ное сложение выполняется в соответствии со статьей 15, а 16-разрядный сдвиг — в соответствии со статьей 34. Преобразо- вание каждого символа из символьного кода в форму целого числа выполняется так, как было описано в статье 33. В этой программе имеется лишь одна команда РНА. Имея в виду правило, по которому все, что загружалось в стек, должно быть выгружено оттуда, посмотрим, где выталкивается содер- жимое регистра А. Через три команды после РНА встречается команда BCS. Если она выполняет переход на метку DECIB, то в этой точке происходит выталкивание (команда PLA). В противном случае выполняется команда PLA через две ко- манды после BCS. Заметьте, что в данном случае команда PLA по метке DECIB не имеет другой цели, кроме как выгрузить то, что было загружено. Обратите также внимание на подпрограмму, которая начи- нается с метки DECI4 и служит для умножения N на 2 и про- верки на переполнение. Если переполнение имеет место, мы должны выйти из подпрограммы DECI, поскольку продолжать бесполезно, но заметьте, что теперь в стеке хранятся два адре- са возврата — один был загружен туда при входе в подпрограм- му DECI, а другой — при входе в подпрограмму DECI4. Поскольку нам надо выйти и из DECI4, и из DECI, мы долж- ны вытолкнуть из стека адрес возврата из DECI4 перед выпол- нением команды RTS. Это делается двумя командами PLA, расположенными как раз перед меткой DECI5 (заметьте, что при выталкивании адреса надо вытолкнуть 2 байта). Этот при- ем можно использовать во всех случаях, когда вам надо сразу выйти из нескольких подпрограмм (разных уровней). В программе используются и другие приемы. Подпрограммы
Программа входного преобразования 229 DECI и DECI4 завершаются со сброшенным флажком перено- са, если только не было переполнения. В подпрограмме DECI флажок переноса сбрасывается командой CLC; в подпрограмме DECI4, если не было переполнения, выполняется переход на RTS командой ВСС DEC15, так что флажок переноса тоже сброшен. Это обстоятельство используется для оптимизации программы: перед первой командой ADC нет необходимости использовать CLC, поскольку флажок переноса уже сброшен. По мере выполнения программы промежуточные результаты сохраняются в различных местах, что экономит время. Рас- смотрим, в качестве примера, команды STA DECI8 и LDY DECI6. Эти две команды перемещают 16-разрядную величину из одного места (ячейка DECI6 и регистр А) в другое (ре- гистр Y и ячейка DECI8). Команда RTS по метке DECI5 используется совместно про- граммой DECI4 и самой DECI (с помощью команды BCS DECIB, см. выше в программе), как это предлагалось в конце предыдущей статьи. Отметьте также пару команд TYA и ТАХ; пересылка величины из регистра Y в регистр X требует двух ко- манд, потому что в процессоре, к сожалению, не предусмотре- на команда TYX. Используя подпрограмму DECI, не забывайте, что она сама использует регистры, не производя при этом сохранения и вос- становления их содержимого. Это же относится и к подпро- граммам DECO и DECOZ, о которых будет идти речь в следую- щей статье. Стоит заметить, что временные переменные в этой програм- ме обозначены DECI6 и DECI8, а не, скажем, TEMPI и ТЕМР2. Это обычный прием для таких подпрограмм, как DECI, кото- рые предназначены для включения в другие программы. Если бы DECI использовала переменную с именем TEMPI, то при включении ее в программу, которая сама использует перемен- ную с тем же именем TEMPI, возникли бы неприятности. Преж- де всего метка TEMPI появилась бы в программе дважды, что недопустимо по правилам языка ассемблера. Даже если обойти это затруднение, неприятности останутся: если наша программа запишет что-то (скажем, z) в ячейку TEMPI, а затем вызовет DECI, то после возврата из DECI программа будет считать, что в TEMPI по-прежнему находится z, в то время как в действи- тельности в TEMPI будет то, что оставила там подпрограмма DECI. Упражнения 1. Представьте, что в программе на рис. 62.1. команда РНА встретилась перед одной или несколькими командами JSR DECI4. Какие еще изменения надлежит сделать, чтобы обеспе- чить аварийный выход из подпрограммы DECI4 (через DECIB)?
230 Статья 63 2. >j< а) Пусть у нас есть три подпрограммы Р, Q и R, при этом Р вызывает Q, a Q вызывает R. Ни одна из подпрограмм не использует РНА или PLA. Пусть теперь внутри подпрограм- мы R имеется аварийный выход для возврата в подпрограмму Р. Как надо организовать аварийный выход, чтобы при возвра- те соблюсти условия нормальной работы стека? б) Предположите теперь, что при аварийном выходе осуществляется возврат в подпрограмму, вызвавшую подпро- грамму Р. Как теперь будет организован аварийный выход? 3. >|< а) Почему на рис. 62.1 отсутствует команда CLC пе- ред второй командой ADC? б) Через четыре команды после метки DECIA стоит команда перехода на метку DECI3 по сброшенному флажку пе- реноса. Если переход произошел, флажок переноса сброшен. Зачем же тогда через три команды стоит команда CLC? >|< в) Почему перед командой RTS (предшествующей метке DECI3) не требуется команда SEC, ведь в этой точке флажок переноса должен быть установлен? Статья 63 Программа выходного преобразования Программа выходного преобразования изображена на рис. 63.1. Она начинается с деления данного числа на 10 000 и получения частного Q и остатка R. Например, 23456, деленное на 10 000, дает 2 и 3456 в остатке. В общем случае частным бу- дет первая цифра, а остатком — оставшаяся часть исходного чис- ла с удаленной первой цифрой. Описанный процесс повторяется еще 3 раза, производя де- ление на 1000, 100 и 10. В результате определяются и выводят- ся на экран 4 цифры. Затем выводится 5-я цифра, представляю- щая собой остаток после деления на 10. Деление осуществляется путем повторения вычитания. Что- бы разделить таким образом М на N, надо выполнить следую- щие шаги (как в статье 37):
Программа выходного преобразования 231 1) установить Q= —1; 2) прибавить 1 к Q; 3) вычесть N из М; 4) если результат не отрицателен, вернуться к шагу 2; 5) прибавить N к М, чтобы получить остаток. Если, например, М=26, a N= 10, то Q и М последовательно примут следующие значения: 1) Q=-l, а М=26; 2) Q=0, а М=16; 3) Q=l, а М=6; 4) Q=2, а М= —4. Мы снова прибавляем 10 к М, чтобы получить остаток Р=6 (частное получилось равным 2, что явля- ется правильным результатом). Вычитание 16-разрядных величин использует процедуру, опи- санную в статье 17; сложение 16-разрядных величин использует процедуру, описанную в статье 15. Прибавление 1 к частному выполняется непосредственно в памяти, как это было предло- жено в конце статьи 57 (для параметра цикла)» Константы 10, 100, 1000 и 10 000 хранятся в двух параллельных массивах 2-байтовых величин (этот вопрос изучался в статье 32). Для описания в программе этих массивов используются псевдоко- манда BYT (см. статью 26) и новая псевдокоманда объявления константы HBY (high-order byte — старший байт). Вспомним из статьи 26, что, написав, например, BYT 11000, мы получим 1 байт, содержащий правые 8 разрядов константы 1000. Напи- сав HBY 11000, мы получим левые 8 разрядов той же константы. (Есть еще псевдокоманда ADR (от address — адрес), не ис- пользуемая в подпрограмме DECO, образующая 16-разрядную константу с переставленными байтами, как они и должны рас- полагаться в адресе. Так, ADR п — это то же самое, что комби- нация псевдокоманд BYT п HBY п*>. Заметьте, что ADR п всегда определяет два байта, даже если п<256. В последнем случае второй из этих двух байтов будет равен 0). Начинающие обычно путают BYT и EQU, и вы должны убе- диться в том, что понимаете разницу. Если мы напишем в на- шей программе N BYT 1100, то команда LDA N загрузит 100; О В языке ассемблера LISA 2.5 вы можете написать ADR i, j, k, ..., что эквивалентно последовательности псевдокоманд ADR i, ADR j, ADR k и т. д.; предусмотрено также еще одно объявление константы DBY («double byte» — двойной байт), выполняющее ту же операцию, что и ADR, но не пере- ставляющее байты адреса. Так, DBY п равносильна HBY п, за которым сле- дует BYT п. Наконец, есть еще псевдокоманда .DA, при этом .DA ана- логична BYT п, .Т)А./п аналогична HYB п, a .DA v (где перед и нет знаков # или /) аналогична ADR и.
; DECO — десятичный вывод (Decimal output) При входе 16-разрядное число в регистрах А и X ; (старшее полуслово — в А, младшее — в X) Выводит число в десятичной форме ' Вход по метке DECOZ начинает вывод с новой строки DECOZ РНА ; ; (CROUT использует регистр A) JSR CROUT ; ; Возврат каретки JMP DECOA ; ; Пропустить следующую команду DECO РНА ; Поместить число на верхушку стека DECOA LDA *!0 ; ; и в регистр X STA DECO9 ; Флажок лидирующих нулей LDY *14 ; 4 десятичных порядка DECO1 LDA *$FF ; Присвоить текущей цифре STA DECO7 ; значение —1, установить флажок SEC ; переноса для вычитания DECO2 INC DECO7 ; Текущая цифра +1 ТХА ; Вычесть степень десяти SBC DECO5—!1,Y ; ; (16-разрядное вычитание) TAX из числа, хранящегося PLA на верхушке стека и в SBC DECO6—!1,Y регистре X РНА Если неотрицательно, BCS DECO2 вычесть снова TXA Флажок переноса здесь ADC DECO5- !1,Y сброшен. Прибавить STA ' DECO8 эту степень десяти PLA и поместить результат ADC DECO6-!1,Y на верхушку стека PHA и в ячейку DECO8 INC DECO9 Установить флажок лидирующих LDA DECO7 нулей, если цифра — BNE DECO3 не нуль. Если нуль, DEC DECO9 восстановить сброшенное состояние BEQ DECO4 флажка и пропустить вывод DECO3 ADC Преобразовать символ (флажок JSR COUT переноса установлен) и вывести на экран DECO4 LDX DECO8 Поместить число на верхушку стека DEY и в регистр X, получить BNE DECO1 следующую степень десяти TXA Последняя цифра в регистре X CLC Сбросить флажок переноса ADC #"0" Преобразовать символ JSR COUT Вывести последнюю цифру PLA Команда, парная к РНА RTS Выход DECO5 BYT 110 Таблица степеней BYT 1100 десяти. Это последовательный BYT 11000 массив 16-разрядных
Программа выходного преобразования 233 Продолжение BYT 110000 ; величин DECO6 HBY 110 ; Сначала расположены HBY 1100 ; младшие полуслова, HBY 11000 ; затем старшие HBY 110000 ; полуслова DECO7 DFS 11 ; Текущая цифра DECO8 DFS 11 ; Временное хранение DECO9 DFS 11 ; Флажок лидирующих нулей COUT EQU $FDED ; Вывод одного символа CROUT EQU &FD8E ; Возврат каретки Рис. 63.1. Программа выходного преобразования. LDA #N загрузит правые 8 разрядов адреса N, что бывает нужно редко. С другой стороны, если мы напишем N EQU 1100, то команда LDA загрузит 100, в то время как команда LDA N загрузит содержимое байта с адресом 100. Это происхо- дит потому, что LDA эквивалентно LDA ф!100, в то время как LDA N эквивалентно LDA 1100. В обоих случаях вместо обозначения N можно непосредственно подставить ! 100, потому что N вполне эквивалентно 1100, что, собственно, и устанавли- вает выражение N EQU !100. Заметьте, что нельзя написать N EQU ф!100— это не допускается. Далее, выражение N BYT 1100 помещает байт данных в программу и потому обычно пи- шется в секции данных, в то время как N EQU ! 100 не помеща- ет никаких данных в программу и может быть написано где угодно, в любом месте программы. Программа DECO использует байт на верхушке стека для хранения частичного результата — в данном случае левое полу- слово М. Эта величина выталкивается, вычитается и проталкива- ется снова последовательностью команд PLA-SBC-PHA. Позже эта величина выталкивается, прибавляется и проталкивается снова последовательностью команд PLA-ADC-PHA. Если верхушка стека содержит частичный результат, эту ячейку надо инициализировать командой проталкивания. Это выполняется в точке DECO; начальное содержимое регистра А представляет собой левое полуслово преобразуемого числа. Заметьте, что программа DECOZ выполняет эту операцию пе- ред вызовом подпрограммы CROUT, которая уничтожает со- держимое регистра А (но не X или Y). То, что было загружено, должно быть выгружено; поэтому команде РНА в начале программы отвечает «парная» команда PLA, в данном случае перед меткой DECO5. Программисту, не знакомому в должной мере с работой стека, эта последняя ко- манда PLA представляется весьма таинственной — она стоит перед самым выходом из программы, но подпрограмма DECO
234 Статья 63 ничего не возвращает, в том числе и с помощью регистра А. Ко- манда PLA нужна только для установки указателя стека так, чтобы команда RTS получила правильный адрес возврата. Программа DECO не выводит лидирующих нулей (см. конец статьи 3). Вместо 00614 она выводит 614; вместо 00207—207. (Заметьте, что нули, не являющиеся лидирующими, как напри- мер, нуль в числе 207, выводятся.) Единственным исключением из этого правила является число 0, которое выводится как 0. В программе DECO предусмотрен флажок лидирующих нулей, DECO9, который устанавливается (т. е. делается отличным от 0), как только встречается цифра, отличная от 0. Пока в ячейке DECO9 нуль, цифры (нули) не выводятся на экран. Выбор име- ни флажка DECO9 напоминает нам выбор имен DECI6 и DECI8 ячеек для временного хранения в предыдущей статье. Заметьте, что установка флажка DECO9 производится одной командой INC DECO9. Упражнения 1. а) Почему в программе на рис. 63.1 команда SEC перед меткой DECO2 не включена в цикл? б) Почему отсутствует команда CLC перед первой коман- дой ADC? в) В комментариях к программе отмечено, что в точке DECO3 флажок переноса всегда установлен, поэтому команда ADC #«0»— !1 фактически прибавляет к содержимому реги- стра А код символа «0». Флажок переноса здесь всегда уста- новлен потому, что последней командой, влияющей на состоя- ние флажка переноса, является предшествующая команда ADC, а эта команда всегда устанавливает флажок переноса. Почему? (Подсказка-, внимательно проследите логическое построение программы.) 2. Пусть в программу включены определения N1 BYT !2 и N2 EQU !2. Справедливы ли следующие утверждения: а) команда ADC #N2 прибавляет 2 (если флажок переноса сброшен) к содержимому регистра А; б) команда SBC #N1 вычитает 2 (если флажок переноса установлен) из содержимого регистра А; в) команда CPY N2 сравнивает содержимое регистра Y с числом 2. 3. Предположите, что по какой-то причине частичный резуль- тат, хранимый в стеке и модифицируемый последовательностью PLA-SBC-PHA, не нуждается в инициализации. Нам, однако, и в этом случае понадобится команда РНА в начале подпрограм- мы и PLA в конце. Почему? (Подсказка-, рассмотрите адрес воз- врата. Укажите определенно, что именно будет выполнено не- правильно, и почему.)
Преимущества аппаратного стека 235 Статья 64 Преимущества аппаратного стека Почему в процессоре 6502 используется такой, казалось бы, сложный аппарат вызова подпрограмм? Почему бы не исполь- зовать способ, принятый в ЭВМ IBM 4300 или CDC 6000? Это очень важный вопрос, заслуживающий подробного ответа, и мы дадим его с помощью примера. На рис. 64.1 показана типичная большая программа, состоящая из основной программы (S1), и 39 программ, представленных на рисунке квадратиками. Если какая-то программа вызывает другую, это обозначается на ри- сунке стрелкой от первого квадратика ко второму. Так, напри- мер, программа S1 вызывает подпрограмму S2; S2 вызывает S3; S3 вызывает S4. Основная программа и различные подпрограммы объедине- ны в уровни. Первый (или самый низкий) уровень включает все подпрограммы, которые сами не вызывают каких-либо про- грамм. Второй уровень включает подпрограммы, которые вы- зывают только подпрограммы первого уровня. Подпрограммы, находящиеся на третьем уровне, могут вызывать только под- программы уровней 1 и 2; и т. д. Любую программу с большим числом подпрограмм можно схематически изобразить таким образом, если только програм- ма не вызывает саму себя как подпрограмму (непосредственно или косвенно). Программа на рис. 64.1 состоит из основной про- граммы и 39 подпрограмм, но содержит только четыре уровня; это типично для программ такого рода — число уровней значи- тельно меньше числа программ. Предположим теперь, что каждая программа сохраняет и восстанавливает содержимое регистров А, X и Y. Если не ис- пользовать стек, то нам придется выделить для этого по 3 бай- та в каждой подпрограмме, а всего 39>|<3=117 байт. Если же использовать стек, нам понадобится только по 3 байта на каж- дый уровень (не считая самого верхнего), т. е. всего 3>{<3 = =9 байт. Почему это так? Каждый раз, когда вызывается подпро- грамма третьего уровня, 3 байта проталкиваются в стек. Когда эта подпрограмма завершается, эти байты выталкиваются. Ког- да вызывается следующая подпрограмма третьего уровня, те же 3 байта стека используются повторно. То же происходит и на более низких уровнях.
рис. 64.1. Несколько уровней подпрограмм.
Преимущества аппаратного стека 237 Аналогичные рассуждения можно провести и для адресов возврата. Если бы команда вызова подпрограммы заносила ад- рес возврата в фиксированное место памяти (вместо стека), нам понадобилось бы одно такое место (т. е. 2 байта) для каждой подпрограммы, а всего 39^2=78 байт*). Используя команду JSR, которая проталкивает адрес возврата в стек, мы можем ограничиться двумя байтами для каждого уровня (не считая на этот раз самого низкого, поскольку подпрограммы самого низкого уровня по определению не содержат команд JSR), а всего 3>|<2=6 байт. Причина здесь та же, что и раньше. Каждый раз при вызове подпрограммы третьего уровня для адреса возврата использу- ются те же самые 2 байта, именно первые 2 байта стека. И, как и раньше, байты стека используются повторно на более низких уровнях. Экономия памяти происходит еще и потому, что команды РНА и PLA 1-байтовые, в то время как STA TEMP и LDA TEMP — 3-байтовые. Полученные в приведенном нами примере цифры экономии памяти можно считать типичными; они ста- новятся еще больше, если в программе больше подпрограмм, что бывает очень часто. Еще одно преимущество стека связано с написанием про- граммы, вызывающей саму себя как подпрограмму; такая про- грамма называется рекурсивной. Рекурсивные программы чаще используются на больших ЭВМ, чем на микроЭВМ, однако ин- тересно отметить, что стек (или его эквивалент) в этих случа- ях необходим. Если программа Р вызывает программу Р и т. д. п раз, то в некоторый момент времени в стеке должны хранить- ся по меньшей мере п различных адресов возврата (а обычно еще и сохраняемые и восстанавливаемые переменные). Упражнения 1. а) Представьте себе, что каждая подпрограмма рис. 64.1 (включая подпрограммы самого нижнего уровня, но исключая основную программу) сохраняет содержимое регистров А, X и Y командами STA, STX и STY и восстанавливает его команда- ми LDA, LDX и LDY. Сколько байтов потребуют все эти ко- манды? *> В некоторых процессорах команды вызова подпрограмм сохраняют ад- рес возврата в регистре. Содержимое этого регистра должно сохраняться и восстанавливаться каждой подпрограммой (кроме подпрограммы самого вы- сокого и самого низкого уровней). Если бы в процессоре 6502 была такая команда, то для этого понадобилось бы 16>(с2=32 байта* 2’. 2) Число 16 имеет случайный характер и соответствует количеству под- программ второго и третьего уровней на рис. 64.1. — Прим, перев.
238 Статья 65 б) Сколько байтов займут соответствующие команды, если использовать стек? (Заметьте, что в этом случае содержимое регистров А, X и Y сохраняется последовательностью команд PHA-TXA-PHA-TYA-PHA и восстанавливается последователь- ностью PLA-TAY-PLA-TAX-PLA. Заметьте также, что каждая подпрограмма, как и в п. а), должна содержать эти последова- тельности.) 2. а) Сколько уровней подпрограмм (см. рис. 64.1) пол- ностью используют стек (считая, что РНА и PLA не использу- ются)? (Не забудьте, что в подпрограмме самого низкого уров- ня нет команд JSR.) б) Сколько уровней подпрограмм полностью используют стек, если каждая подпрограмма (включая подпрограммы са- мого нижнего уровня, но исключая основную программу) сохра- няет в стеке содержимое регистров А, X и Y? >}с 3. Мы отмечали выше, что любую программу с большим числом подпрограмм можно схематически изобразить так, как это показано на рис. 64.1, если только ни одна программа не вызывает саму себя как подпрограмму. Почему необходима эта оговорка? (Подсказка: внимательно прочитайте определение уровня подпрограмм.) Статья 65 Двоично-десятичные числа и десятичный режим В этой и следующих трех статьях мы рассмотрим последние несколько команд процессора 6502: CLD, SED, CLI, SEI, RTI, РНР и PLP. После этого мы перейдем к более сложным слу- чаям программирования с использованием рассмотренных нами ранее команд. Мы уже рассматривали целые числа без знака (имеющие диапазон от 0 до 255 и размещаемые в 1 байте) и целые числа со знаком (диапазон от —128 до 127). Кроме них, используют- ся также двоично-десятичные числа в диапазоне от 0 до 99. Двоично-десятичное число всегда состоит из двух цифр, причем каждая цифра хранится в четырех двоичных разрядах данного
Двоично-десятичные числа и десятичный режим 239 байта. Так, двоичный код 10010000 соответствует двоично-деся- тичному числу 90, так как первая цифра (9) имеет двоичную форму 1001, а вторая цифра (0) —двоичную форму 0000 (тот же двоичный код 10010000 представляет целое без знака 144, или целое со знаком — 112). Двоично-десятичные числа напоминают шестнадцатеричные, и, действительно, в ряде случаев обращение к ним производит- ся одинаково. Например, команда LDA #$25 загружает шест- надцатеричное 25, или двоичное 00100101, в регистр А, где оно выглядит как двоично-десятичное 25. В общем случае двоично- десятичные константы записываются, как если бы они были шестнадцатеричными константами (однако, имеется только 100 законных двоично-десятичных чисел, и такие шестнадцатерич- ные константы, как 7С, F9 или ВА, не соответствуют каким-ли- бо двоично-десятичным числам). Рассмотрим теперь сложение двух двоично-десятичных чи- сел. Если регистр А содержит $25 и мы прибавляем еще $25, то получается $4А, или 01001010 в двоичной форме, но если 25 — двоично-десятичное число, то результат сложения должен быть равен 50 (01010000 в двоичной форме). Чтобы добиться этого, мы используем специальный флажок десятичного режима. Как и флажок переноса, флажок десятичного режима может быть установлен (в 1) или сброшен (в 0) с помощью команд: CLD Сброс флажка десятичного режима SED Установка флажка десятичного режима Обычно флажок десятичного режима сброшен. Если он уста- навливается, команды ADC и SBC прибавляют и вычитают дво- ично-десятичные числа и результатом операции также является двоично-десятичное число. Заметьте, что это относится только к командам ADC и SBC, но не к командам инкремента, декре- мента и сдвига. Если включен десятичный режим (т. е. установлен флажок десятичного режима), флажок переноса работает, как обычно. Если флажок переноса установлен, команда ADC прибавляет дополнительную 1 (как двоично-десятичное число). Если ре- зультат г меньше 100 (двоично-десятичного), флажок переноса сбрасывается, в противном случае он устанавливается, а в ре- гистр А заносится г—100. Аналогично этому, если флажок пе- реноса сброшен (указывая на состояние займа), команда SBC вычитает дополнительную 1 (как двоично-десятичное число). Если результат г больше или равен 0, флажок переноса устанав- ливается; в противном случае он сбрасывается (указывая на со- стояние займа), а в регистр А заносится г+ЮО. При включенном десятичном режиме флажки нуля, знака и
240 Статья 65 переполнения хотя и устанавливаются командами ADC и SBC, однако не несут в себе полезной информации1). Поэтому после сложения или вычитания в десятичном режиме нельзя пользо- ваться командами BEQ, BNE, BPL, BMI, BVC или BVS. Далее, если при сложении или вычитании в десятичном режиме с по- мощью команд ADC или SBC хотя бы одна из величин не яв- ляется законным двоично-десятичным числом, результат в ре- гистре А не будет иметь смысла (но вычисления не остано- вятся). Если при включенном десятичном режиме мы вычитаем чис- ло п из нуля, то получается дополнение до десяти этого числа, или 100—п. Десятичные числа со знаком можно представлять в форме дополнения до десяти, так же как и обычные целые чис- ла со знаком представляются в форме дополнения до двух. Это, однако, бывает очень редко. Счет числа шагов в цикле почти никогда не выполняется в двоично-десятичной форме, потому что содержимое счетчика цикла обычно изменяется командами инкремента или декремен- та, а эти команды, как мы видели, не работают в десятичном режиме. С другой стороны, двоично-десятичные числа можно срав- нивать при помощи команд СМР, СРХ или CPY. Это возможно потому, что соотношения («меньше чем», «больше чем» и т. д.) между двумя 8-разрядными величинами остаются в силе неза- висимо от того, рассматриваются ли эти величины как обычные числа без знака или как двоично-десятичные числа. В процессоре не предусмотрены команды проверки состоя- ния флажка десятичного режима (типа команд ВСС и BCS). Однако такую проверку можно выполнить косвенным образом (см. статью 67). Многие программисты предпочитают начинать все основные программы командой CLD, чтобы застраховать себя от неприятностей, если предыдущий пользователь ЭВМ оставил десятичный режим включенным. Заметьте, что в десятичном режиме сдвиг влево не умножа- ет на 2. Если вы хотите умножить двоично-десятичное число на 2, сложите его в десятичном режиме с самим собой (однако 4 сдвига вправо делят двоично-десятичное число на 10). Упражнения 1. Сложите данные ниже двоично-десятичные числа при включенном и выключенном десятичном режиме. В каждом слу- чае приведите результат сложения в двоично-десятичной форме и окончательное состояние разряда переноса: Если, например, А=99 (двоично-десятичное), то прибавление 1 запи- шет 0 в регистр А, но флажок нуля в этом случае не установится.
Упакованные строки 24 i а) 39 и 39; * б) 77 и 76; в) 92 и 94. 2. Выполните вычитание данных ниже двоично-десятичных чисел при включенном и выключенном десятичном режиме. В каждом случае приведите результат вычитания в двоично-де- сятичной форме и окончательное состояние разряда переноса: * а) 61-49; б) 23-58; * в) 14-81. 3. Пусть байт EIGHT содержит константу 8. Тогда приве- денная ниже программа делит двоично-десятичное число К на 2: LDA К LSR BIT EIGHT BEQ BETA SEC SBC #!3 BETA STA К Как она работает? Объясните в деталях. (Подсказка: возьмите двоично-десятичное число ab и рассмотрите два случая — когда а четно, и когда а нечетно.) В частности, почему в команде SBC используется константа 3? Статья 66 Упакованные строки Зачем нам вообще надо использовать двоично-десятичные числа? Причина заключается в том, что многоразрядные двоич- но-десятичные числа широко используются в различных устрой- 16-589
242 Статья 66 ствах, например калькуляторах, работающих с десятичными числами. Рассмотрим 8-разрядное десятичное число, например 93 000 000. Мы можем хранить это число в 8 байтах так, что в каждом байте записан символьный код одной десятичной цифры х \ л* ’ V ‘ V V л* "эп •з" "о" "0е "О' "0“ "о" "о" о°° о°° о°° \\° Л° \Х° \О' чо* \Х X X чо° 5> # # Рис. 66.1. X X {рис. 66.1). Можно, однако, упаковать это число в 4 байта так, что в каждом байте будут записаны 2 десятичные цифры (т. е. ‘ 1 двоично-десятичное число) (рис. 66.2). Такая форма записи известна под названием упакованной строки в противополож- ность неупакованной строке предыдущего примера. В общем т Т» ! I т+ '2 т+•з 10010011 00000000 00000000 00000000 9 1 3 0 | 0 0 | 0 0 | 0 Рис. 66.2. «случае n-байтовая упакованная строка содержит 2п десятичных цифр, и их можно распаковать в 2п-байтовое число, состоящее из символьных кодов отдельных десятичных цифр. Пусть нам надо сложить две 5-байтовые упакованные стро- ки. Используя десятичный режим, мы можем складывать строки по две цифры сразу, справа налево — сначала две самые пра- вые цифры обеих строк, затем следующие две и т. д. При этом используется то обстоятельство, что команда ADC одинаково правильно работает с флажком переноса при многоразрядном сложении (устанавливает флажок переноса и учитывает его со- стояние) независимо от состояния флажка десятичного режима. Преобразование упакованных строк в распакованную форму либо распакованных строк в упакованную форму осуществля- ется просто и быстро. Мы попросту преобразуем 1 байт в два, либо 2 байта в один, а для J-байтовой строки повторяем этот процесс J раз. Гораздо больше времени требует преобразование упакованных или распакованных строк в двоичную (внутрен- нюю) форму, так же как и обратное преобразование. Мы уже рассматривали в статьях 62 и 63 несколько программ преобра-
Упакованные строки 243 зования такого рода; эти программы предназначены лишь для преобразования 2-байтовых величин во внутренней форме. В об- щем случае для многобайтовых величин 2-байтовые операции сложения и вычитания в этих программах должны быть заме- нены многобайтовыми операциями, что существенно замедляет их выполнение. Это позволяет понять причину использования двоично-деся- тичных чисел и упакованных строк. Рассмотрим, например, калькулятор. В нем имеются устройство индикации десятичных чисел и клавиатура для их ввода (также в виде десятичных цифр). МикроЭВМ, входящая в состав калькулятора, может прибавлять числа, вводимые с клавиатуры, к содержимому уст- ройства индикации. Если бы сложение выполнялось в двоичной внутренней форме, вводимые числа надо было бы преобразовать в эту форму, а результат сложения преобразовывать в форму строкй десятичных цифр. Гораздо проще и быстрее хранить все числа в калькуляторе в виде упакованных строк и выполнять сложение так, как было описано выше. Заметьте, что в десятичном числе, хранящемся в двух бай- тах, байты не переставлены; если адреса этих байтов D и D-H1, то D — это старший байт, a D+!l—младший. Это, разумеет- ся, справедливо и для многобайтовых величин: первый байт всегда является самым старшим, а последний — самым млад- шим. Распакованные строки тоже могут непосредственно участво- вать в операциях сложения, однако здесь следует соблюдать осторожность. Складывая, например, 5 и 7, мы в действитель- ности складываем их символьные коды, т. е. В5 и В7 (шест- надцатеричные). Для получения правильного результата следу- ет вычесть ВО из второго кода, а затем уже выполнить сложе- ние. Если результат (В5+07=ВС в данном случае) окажется больше, чем В9 (символьный код числа 9), то следует вычесть 10 (после чего в нашем примере получится В2) и сохранить признак переноса для следующей по порядку операции сложе- ния (передвигаясь по строке справа налево). Упражнения >|< 1. Напишите программу преобразования распакованной строки В длиной К в соответствующую упакованную строку N. Пусть для определения В и N используется предложение В DFS ! 100 и N DFS 150, а К (К^ЮО) —четное число. Исполь- зуйте цикл, заканчивающийся командами DEX и BNE. 2. Напишите программу вывода на дисплей упакованной строки длиной М с помощью подпрограммы COUT. Проследи- те, чтобы выводился символьный код каждой цифры, а не сама 16*
244 Статья 67 цифра. Используйте цикл, заканчивающийся командами INY, CPY М и BNE. 3. Напишите программу сложения J-байтового числа, начи- нающегося с адреса N1, и J-байтового числа, начинающегося с адреса N2 с образованием J-байтового числа по адресу N3. (Не забудьте о том, что байты этих чисел не переставлены.) Статья 67 Регистр состояния В состав процессора 6502 наряду с другими входит регистр, называемый регистром состояния. Иногда его называют также регистром флажков, поскольку он представляет собой не что иное, как совокупность различных флажков состояния, объеди- ненных водном регистре. Регистр состояния показан на рис. 67.1. S V В D I Z С Рис. 67.1. Ранее уже шла речь о флажке знака (S), флажке перепол- нения (V), флажке десятичного режима (D), флажке нуля (Z) и флажке переноса (С). Флажок внутреннего прерывания В (break flag) устанавливается в 1 всякий раз, когда происходит внутреннее прерывание; назначение флажка внешнего прерыва- ния I (interrupt flag) будет обсуждаться в следующей статье. Регистр состояния имеет и другие названия: слово состояния программы1) (program status word) или PSW* 2>, а также просто регистр Р; при пошаговом выполнении программы в табличке, появляющейся на экране дисплея, этот регистр обозначается символом Р (см. статью 47). В системе команд процессора 6502 '* Данная терминология заимствована у фирмы IBM, несмотря на то что в вычислительных машинах этой фирмы слово состояния программы подра- зумевает нечто большее, чем в процессоре 6502, и содержит больше инфор- мации. 2) Чаще эту аббревиатуру расшифровывают как processor status word — слово состояния процессора. — Прим, перев.
Регистр состояния 245 отсутствуют команды пересылки, с помощью которых содержи- мое регистра Р (условимся называть его именно так) можно было бы пересылать в какие-либо другие регистры и наоборот. Вместо этого имеется возможность проталкивать содержимое регистра Р в стек, а также извлекать содержимое верхушки стека и заносить его в регистр Р подобно тому, как это делает- ся для регистра А. Указанные действия выполняются посред- ством следующих команд: РНР Проталкивание в стек содержимого регистра Р (регистра состояния) PLP Выталкивание из стека содержимого регистра Р (регистра состояния) Следовательно, самый простой способ пересылки содержимого регистра Р в регистр А заключается в последовательном приме- нении команд РНР и PLA. Обратная пересылка из А в Р осу- ществляется аналогично с помощью команд РНА и PLP. Мы уже видели, что в системе команд процессора 6502 от- сутствует команда перехода по состоянию флажка десятичного режима. Однако данная операция может быть реализована по- средством четырех команд следующим образом: РНР ; Переслать содержимое per. Р в стек, PLA ; а затем обратно в регистр А AND 4# %00001000 ; Выделить четвертый бит справа (D) BNE ALPHA В данном случае переход по адресу ALPHA осуществляется, только если установлен десятичный режим. Аналогичным обра- зом можно, конечно, поступать и для других флажков регистра Р, однако, как правило, необходимости в этом не возникает. В процессоре 6502 регистр Р используется для решения од- ной непростой задачи, связанной с десятичным режимом. До- пустим, что при выполнении некоторой подпрограммы был включен десятичный режим. Надо ли выключить десятичный ре- жим по окончании этой подпрограммы? Все зависит от того, был ли десятичный режим включен в вызывающей программе; однако установить это без дополнительных действий не пред- ставляется возможным. Один из вариантов решения указанной задачи, очевидно, заключается в том, чтобы в начале подпрограммы воспользо- ваться приведенной выше последовательностью команд для ана- лиза соответствующего разряда регистра Р. Это позволит вам зафиксировать, был ли десятичный режим включен или выклю- чен, и выполнить соответствующие действия в конце данной
246 Статья 67 подпрограммы. Однако более предпочтителен вариант, в кото- ром содержимое регистра Р с помощью команды РНР в начале подпрограммы запоминается в стеке, а затем посредством ко- манды PLP в конце подпрограммы извлекается из стека. В этом случае десятичный режим можно включать и выключать в лю- бом месте внутри данной подпрограммы. Команды РНР и PLP можно использовать также для сохра- нения и восстановления других флажков. Пусть, например, вы определили значение флажка переноса, которое позже потре- буется в вашей программе. Если затем в программе будут вы- полняться операции сдвига, то значение флажка переноса из- менится. Для предупреждения возможной в этом случае ошиб- ки предполагается следующая последовательность действий: с помощью команды РНР занести содержимое регистра Р в стек, выполнить операцию сдвига, а затем посредством команды PLP восстановить содержимое регистра Р. Аналогичный прием мож- но применять и для других флажков. Во время отладки программ состояние тех или иных флаж- ков можно проверять, наблюдая содержимое регистра Р на экране дисплея. Пусть, например, имеется некоторая команда, после выполнения которой флажок переполнения должен быть сброшен. Если же после этой команды на экране дисплея вы- свечивается сообщение Р=75, то можно говорить, что где-то произошла ошибка, поскольку шестнадцатеричное число 75 в двоичном представлении имеет вид 011101001, откуда следует, что флажок переполнения V (второй разряд слева на рис. 67.1) в данном случае установлен. Упражнения 1. Назовите отдельную команду процессора 6502, которая выполняет условный переход при тех же условиях, что и еле- дующие совокупности команд: а) РНР PLA AND #$40 BNE ALPHA * б) РНР PLA AND #$2 BEQ ALPHA в) PHP PLA
Прерывания 247 AND #$80 BNE ALPHA 2. Назовите отдельную команду процессора 6502, которая выполняет установку или сброс определенного флажка так же, как и приведенные ниже последовательности команд: * а) РНР PLA AND #$BF РНА PLP б) РНР PLA ORA #$8 РНА PLP * в) РНР PLA AND #$FE РНА PLP 3. Пусть в начале некоторой программы последовательно в указанном порядке выполняются команды РНР и РНА, а в конце—PLP, PLA и RTS. Это скорее всего неверно. Почему? (Если ответ неочевиден, выполните ручную прогонку програм- мы, как это делалось в статье 44.) Статья 68 Прерывания Во время выполнения той или иной программы происходят события, на которые вычислительная машина должна немедлен- но отреагировать. К ним относятся ввод данных через клавиа-
248 Статья 68 туру ЭВМ, выполнение операций с дисками, нажатие кнопки пользователем, принимающим участие в компьютерной игре, по- явление сигнала предварительного предупреждения о том, что через несколько миллисекунд произойдет аварийное отключение питания и т. п. Во всех подобных случаях в процессоре 6502 предусмотрено прерывание выполняющейся программы и вызов программы специального типа, называемой программой обра- ботки прерывания, которая и выполняет необходимые для кон- кретной ситуации действия. После завершения работы програм- мы обработки прерывания управление вновь передается той про- грамме, которая выполнялась непосредственно до прерывания. В процессоре 6502 определены прерывания трех типов. К первому относятся прерывания типа IRQ; адрес соответству- ющей программы обработки прерывания хранится в ячейках с адресами FFFE и FFFF (с переставленными байтами). Если при этом пользователь нажимает, например, какую-либо кнопку, ЭВМ приостанавливает текущую обработку, обращается к ячей- кам с адресами FFFE и FFFF, в которых хранится адрес а, и вызывает программу обработки прерывания, выполнение кото- рой начинается с команды, имеющей этот адрес а. (Наименова- ние IRQ обозначает interrupt request — запрос на прерывание.) В системе Эпл имеется своя специфическая программа обра- ботки прерываний типа IRQ; ей соответствует адрес а, который записан в ячейках с адресами FFFE и FFFF. Изменить этот ад- рес нельзя, поскольку две указанные ячейки (а на самом деле все ячейки с адресами от С100 до FFFF) зарезервированы для системы Эпл и расположены в блоке постоянного запоминаю- щего устройства (ПЗУ),что и объясняет невозможность измене- ния их содержимого (более подробно ПЗУ обсуждаются в ста- тье 78). Из программы обработки прерывания типа IRQ в си- стеме Эпл осуществляется вызов другой подпрограммы, адрес которой хранится в ячейках памяти с адресами 03FE и 03FF; обычно это адрес еще одной подпрограммы монитора системы Эпл, но он может быть модифицирован, поскольку ячейки с ад- ресами 03FE и 03FF находятся не в ПЗУ. Это значит, что для системы Эпл вы можете написать свои собственные программы обработки прерываний, правда, острой необходимости в этом, как правило, нет. Между обычной подпрограммой и программой обработки прерывания существует принципиальное отличие. По заверше- нии обычной подпрограммы и выходе из нее в зависимости от замысла программиста содержимое регистров может остаться неизменным, а может и измениться. В большинстве подпро- грамм предусматривается сохранение и восстановление содер- жимого регистров, а в некоторых подпрограммах, например MULT и DIV, это не делается. Однако после выполнения про-
Прерывания 249 граммы обработки прерывания содержимое всех регистров, в том числе и регистра с флажками состояний, должно остаться неизменным, поскольку не существует никаких средств для то- го, чтобы определить, какие действия выполнялись в ЭВМ в мо- мент возникновения прерывания. При вызове подпрограммы сигналом прерывания осуществ- ляется занесение содержимого регистра состояний в стек точно так же, как если бы выполнялась команда РНР. Для обеспече- ния возврата из программы обработки прерывания существует специальная команда: RTI Возврат из программы обработки прерывания При выполнении команды RTI сначала предыдущее содержи- мое регистра состояния выталкивается из стека, а затем осу- ществляется непосредственно возврат из программы. Следует отметить, что для возврата из программы обработки прерыва- ния должна использоваться команда RTI, а не пара команд PLP и RTS, поскольку при вызове программы обработки пре- рывания в стек заносится значение адреса возврата, а не зна- чение адреса возврата, уменьшенное на 1, как это делается при использовании команды JSR. Таким образом, в отличие от ко- манды RTS при выполнении команды RTI прибавление 1 к зна- чению извлеченного из стека адреса возврата не производится. Система прерываний может быть целиком выключена (т е. прерывания запрещены) посредством одного из флажков со- стояния, а именно флажка состояния прерывания. Значение этого флажка равно 0, если система прерываний включена, и 1, если система прерываний выключена. Установка и сброс флажка состояния прерываний I осуществляется с помощью следующих двух команд: CLI Сброс флажка I (включение системы прерываний) SEI Установка флажка I (выключение системы прерываний) При вызове программы обработки прерывания.происходит вы- ключение системы прерываний, как если бы выполнялась ко манда SEI. В результате этого сама программа обработки пре рывания не может оказаться прерванной1). Это, однако, выпол- няется после того, как содержимое регистра Р проталкивается в стек; поэтому когда по команде RTI содержимое регистра Р будет извлечено из стека, система прерываний будет вновь включена, т. е. прерывания будут разрешены, поскольку флажок В отличие от этого в других ЭВМ программы обработки прерываний могут оказаться прерванными; при этом каждому прерыванию ставится в соответствие некоторый приоритет, и прерывания с более высоким приорите- том могут приводить к прерываниям программ обработки прерываний с бо- лее низким приоритетом.
250 Статья 68 состояния прерываний в регистре Р очевидно сброшен. (Отме- тим, что во многих процессорах, отличных от 6502, перед самым возвратом из программы обработки прерывания приходится вы- полнять действия, эквивалентные команде CLI.) Из некоторых программ обработки прерываний возврат (по- средством команды RTI )не производится вовсе. В частности, нажатие на клавиатуре кнопки начальной установки, например чтобы выйти из бесконечного цикла, вызывает прерывание, и система Эпл дает вам возможность продолжить отладку с этой точки. Если бы здесь была выполнена команда RTI, это вернуло бы нас назад в тот же бесконечный цикл. Прерывания началь- ной установки составляют вторую группу прерываний процессо- ра 6502; при этом адрес соответствующей программы обработ- ки прерывания находится в ячейках с адресами FFFC и FFFD. Наконец, существуют прерывания, которые не могут быть запрещены даже командой SEI. Такие прерывания называются немаскируемыми (non-maskable interrupts), или прерываниями типа NMI. Они образуют третью группу прерываний процессо- ра 6502; адрес соответствующей им программы обработки пре- рывания хранится в ячейках с адресами FFFA и FFFB. Преры- вание по сигналу, предупреждающему об отключении питания, должно быть немаскируемым, причем желательно, чтобы это прерывание могло приостанавливать выполнение даже про- грамм обработки других прерываний. В системе Эпл програм- ма обработки прерываний типа NMI вызывает подпрограмму, адрес которой хранится в ячейках памяти с адресами 03FC и 03FD так же, как и в случае прерываний типа IRQ. Это дает возможность программисту писать свои собственные програм- мы обработки прерываний типа NMI, хотя на практике этим пользуются нечасто. Упражнения 1. Пусть в стеке процессора 6502 хранятся следующие величины, представленные в шестнадцатеричном виде: Адрес 01F0 01F1 Содержимое А7 D4 01F2 55 01F3 26 01F4 В1 01F5 ЗС 01F6 08 01F7 9Е
Команды ввода данных 251 Пусть теперь выполняется команда LDA =£!(), расположенная в ячейках памяти с адресами 0832 и 0833; после выполнения этой команды в регистре Р будет содержаться значение 31, а в регистре S—F3. Предположим, что в этой точке возникает пре- рывание. Укажите новое содержимое отмеченных ячеек стека, а также регистров Р и S перед началом выполнения программы обработки прерывания. Обоснуйте свой ответ. (Не забудьте, что система прерываний будет выключена, и это должно быть от- ражено в новом содержимом регистра Р.) 2. Пусть в ячейках памяти с адресами от 01F0 до 01F7 хра- нится та же информация, что и в предыдущем упражнении, а в регистрах Р и S — соответственно В7 и F3. Пусть далее выпол- няется команда RTI. Укажите новое содержимое регистров Р и S и определите адрес, по которому произойдет переход в ре- зультате выполнения команды RTI. Обоснуйте свой ответ. >{<3. Пусть выполняется команда, имеющая трехбайтовый формат и занимающая ячейки памяти с адресами 08В0, 08В1 и 08В2. Если в этот момент возникает прерывание, то адрес воз- врата, который при этом помещается в стек, не всегда будет равен 08ВЗ. Почему? Статья 69 Команды ввода данных В каждой вычислительной машине имеются команды, пред- назначенные для выполнения операций ввода и вывода данных. Конкретно в системе Эпл все операции ввода и вывода выпол- няются с помощью подпрограмм, таких, как RDKEY и COUT; однако нам было бы полезно узнать кое-что о командах, ис- пользуемых в этих подпрограммах. Отметим, что в данной книге не ставится цель — обучить са- мостоятельному написанию подпрограмм ввода-вывода на язы- ке ассемблера процессора 6502, поскольку для этого необходи- мо детальное изучение аппаратного обеспечения этого процес- сора, что существенно выходит за рамки настоящей книги1*. о Хорошее описание аппаратного обеспечения процессора 6502 приведе- но в книге [12].
252 Статья 69 Это связано с тем, что действие специальных команд, использу- емых в подпрограммах ввода-вывода, зависит от организации связи процессора 6502 с клавиатурой, экраном дисплея, печа- тающим устройством и т. д. Причем она значительно изменяет- ся для различных систем, реализованных на базе процессора 6502. Поэтому, например, в системах Эпл, Атари 800 и VIC-20 используются свои специфические подпрограммы ввода-вывода, каждая из которых предназначена только для какой-то опреде- ленной системы. Здесь же можно говорить только об общих принципах организации ввода-вывода в процессоре 6502. Прежде всего отметим, что ввод-вывод в любой микроЭВМ, как правило, выполняется посимвольно, т. е. по одному символу за раз. Если требуется ввести несколько символов (как, напри- мер, в подпрограмме GETLNZ), то необходимо организовать цикл, содержащий команды для чтения одного символа. (Это отличает микроЭВМ от большинства больших ЭВМ, в которых одной командой осуществляется ввод целого массива.) Процедура считывания одного символа обычно состоит из следующих шагов: 1) проверка того, нажал ли оператор, работающий за кла- виатурой, очередную клавишу; 2) если нет, возврат на шаг 1; 3) считывание очередного символа. На шаге 3 регистр А загружается содержимым ячейки па- мяти, которую обозначим именем INDATA (от input data — входные данные) и которая имеет фиксированный адрес (точно так же, как стек имеет фиксированные адреса от 0100 до 01FF). Соединения в ЭВМ1* выполнены таким образом, что обращение по этому адресу вызывает ввод очередного символа. (Конкрет- ное значение адреса может отличаться для различных систем на основе процессора 6502.) На шаге 1 в регистр А загружается содержимое некоторой другой ячейки, имеющей также фиксированный адрес и кото- рую будем обозначать именем INSTAT (от input status — состо- яние ввода). Соединения в ЭВМ выполнены таким образом, что обращение по этому адресу вызывает загрузку содержимо- го регистра состояния клавиатуры (не путайте этот регистр с регистром Р). Один из разрядов этого регистра состояния пред- ставляет собой флажок готовности. Когда на клавиатуре нажи- мается очередная клавиша с символом, флажок готовности ус- танавливается (с помощью электронных цепей клавиатуры, а не программы пользователя). После считывания этого символа (шаг 3) флажок готовности сбрасывается (опять автоматичес- ки аппаратно, а не программно). о Не ЭВМ Эпл. Объяснения см. далее.
Команды ввода данных 253 Наконец, на шаге 2 выполняются команды проверки разря- дов и условного перехода (см. статью 52). Таким образом, ти- пичная программа считывания символа может иметь следую- щий вид: IWAIT LDA INSTAT ; Получить флажок ввода AND #%00000001 ; Выделить самый правый разряд BEQ IWAIT ; Если 0, продолжать проверку LDA INDATA; Если 1, ввести символ Еще раз обращаем внимание на то, что это только типичная программа; многие детали могут определяться особенностями конкретного конструктивного варианта системы на базе процес- сора 6502. Перечислим некоторые из этих особенностей. 1) Конкретные адреса ячеек INSTAT и INDATA. 2) Номер разряда в ячейке INSTAT, используемого в каче- стве флажка готовности (в приведенном выше примере это был самый правый разряд). 3) Состояние готовности может обозначаться единицей, а состояние неготовности — нулем (в приведенном выше примере- это именно так) или наоборот. 4) Описанная выше схема организации ввода может вооб- ще не использоваться. Например, в другом варианте схемы ор- ганизации ввода каждый вводимый символ представляется по- средством только семи разрядов, а восьмой (самый левый) раз- ряд используется в качестве флажка готовности. В этом случае в оперативной памяти существует только одна специальная ячейка; если и назвать INDATA и считать, как и раньше, что> значение 1 в крайнем левом разряде этой ячейки означает со- стояние готовности, то те же действия, что и в предыдущем примере, можно выполнять с помощью следующих команд: IWAIT LDA INDATA ; Получить данные вместе с флажком ; готовности BPL IWAIT ; Продолжить, только если флажок был ; установлен Такая схема организации ввода применяется в системе Эпл, при этом адрес ячейки INDATA в шестнадцатеричном виде равен С000. 5) Приходится учитывать и другие специфические черты ап- паратного обеспечения. Например, в системе Эпл предусмотре- на операция, называемая «сброс строба клавиатуры», которая должна выполняться сразу же вслед за операцией считывания какого-либо символа. В силу особенностей аппаратной органи- зации процессора Эпл эта операция выполняется посредством
254 Статья 69 команды BIT $С010; с помощью этой команды осуществля- ется сброс знакового бита ячейки INDATA, так что пока не бу- дет нажата какая-нибудь клавиша, этот бит будет указывать на состояние неготовности. Еще одно обстоятельство связано с использованием разряда готовности не в качестве флажка, а как источника прерывания. Когда с клавиатуры вводится но- вый символ, возникает прерывание, как это описывалось в пре- дыдущей статье. Программа прерывания обеспечивает считы- вание символа и занесение его в массив, чтобы его можно было позже обработать выполняемой программой. В других микрокомпьютерах, таких, как Z80, имеются спе- циальные команды для ввода символа. В процессоре 6502 ввод символа выполняется с помощью аппаратно реализованных ко- манд загрузки, как это описывалось выше; про такой ввод-вы- вод говорят, что он организован по аналогии с обращением к памяти. Программирование с использованием флажка готов- ности, как в приведенном выше примере, носит название про- граммирования по готовности. Упражнения 1. Дополните строки ввода с клавиатуры первого примера настоящей статьи, превратив его в упрощенный вариант GETLN подпрограммы монитора Эпл, предназначенной для приема стро- ки входных символов (включая завершающий возврат каретки) и пересылки ее в ячейки памяти с адресами от 0200 до 0200+^; входная строка содержит п символов плюс возврат каретки. (Не заботьтесь о том, что действительная подпрограмма GETLN монитора системы Эпл допускает возврат на шаг и пов- торный ввод символа; кроме того, не начинайте вашу программу с возврата каретки, как это делается в подпрограмме GETLNZ.) 2. Перепишите последовательность строк ввода в первом примере данной статьи так, чтобы сэкономить один байт. (Под- сказка-. используйте операцию сдвига.) 3. а) Пусть в системе Эпл флажок готовности (самый ле- вый разряд ячейки INDATA) принимает значение 0 для состоя- ния «готово» и 1 для состояния «не готово» (а не наоборот). В последовательности команд IWAIT LDA INDATA ; Получить данные вместе с флажком ; готовности BPL IWAIT ; Продолжить, только если флажок установлен придется заменить команду BPL на BMI. Какая ошибка оста- нется при этом в программе? (Подсказка-, воспользуйтесь табл. П9 и П10.) б) Как можно устранить эту ошибку?
Команды вывода данных 255* Статья 70 Команды вывода данных Между командами ввода и вывода данных в процессоре 6502 существует много общего. Так же как и ввод, вывод вы- полняется посимвольно. Далее, в процессоре 6502 отсутствуют команды, которые бы использовались исключительно для выво- да данных. Вместо этого функции команды вывода выполняет, как правило, команда записи, по которой информация записы- вается в ячейку памяти с фиксированным адресом. Аналогия между операциями загрузки и записи, с одной сто- роны, и процедурами ввода и вывода, с другой, имеет большое значение. Рассмотрим, к примеру, вывод данных на печатающее уст- ройство. Это устройство имеет определенное максимальное бы- стродействие скажем 30 симв./с. Каждый символ передается с помощью десяти битов (восемь представляют непосредственно сам символ, и два дополнительных служат целям синхрониза- ции), поэтому быстродействие равно 300 бит/с или 300 бодам (бод — единица измерения скорости передачи данных, назван- ная так по имени французского ученого Бодо; точно так же как единица напряжения вольт получила свое название в честь А. Вольта, а единица электрической емкости фарада — в честь М. Фарадея). Скорости, равной 30 симв./с, соответствует передача одного символа за каждые 34 100 машинных циклов (поскольку 34100=1023 000/30, а в одной секунде содержится 1023 000 ма- шинных циклов — см. статью 36). После того как один символ послан, процессор должен ожидать 34 100 машинных циклов перед тем, как послать следующий. Если в процессоре не должны выполняться еще какие-нибудь операции, то самым простым средством для организации ожи- дания в течение 34 100 (или любого другого числа) машинных циклов является программный цикл ожидания. Он представля- ет собой самый обычный многократно выполняющийся цикл, в котором не выполняется никаких действий. Приведем пример типового цикла ожидания для процессора 6502: LDA L ; Установить счетчик 1 STA I ; равным L LI LDA М ; Установить счетчик J, STA J ; равным М
256 Статья 70 L2 LDA N STA К L3 DEC К BNE L3 DEC J BNE L2 DEC I BNE LI Установить счетчик К, равным N Уменьшить содержимое счетчика и вернуться на начало цикла Уменьшить содержимое второго счетчика и вернуться на начало цикла Уменьшить содержимое первого счетчика и вернуться на начало цикла В данном программном цикле организовано ожидание в тече- ние приблизительно 8^iL>|<M^«N машинных циклов (на самом деле в течение 8>J<L5|<M>|«N+15>|«L:4iM+15>J<L4-8 циклов). Тогда требуемое значение 34 100 получается для L = 12, М=6 и N = 57. В мониторе системы Эпл имеется свой цикл ожидания. По команде JSR WAIT (где WAIT EQU $FCA8) организуется ожидание в течение (26-|-27А-]-5&2)/2 машинных циклов, где k равно исходному содержимому регистра А. Для того чтобы обеспечить ожидание в течение времени, чуть меньшего, чем 34 100 машинных циклов, можно загрузить в регистр А десятич- ное число 114, а затем выполнить обращение к WAIT. Для выполнения синхронизированного вывода циклы ожида- ния не являются единственным средством. Многие устройства вывода после истечения определенного интервала времени, ког- да они переходят в состояние готовности для приема нового символа, вырабатывают сигнал. Этот сигнал устанавливает фла- жок готовности вывода в регистре состояния. Установка флаж- ков готовности вывода и готовности ввода происходит совершен- но разными способами, но обработка этих флажков в соответ- ствующих программах, использующих их, выполняется одина- ково; сначала отрабатывается ожидание, пока соответствующее устройство не перейдет в состояние готовности, а затем выпол- няется либо ввод (с помощью операции загрузки), либо вывод (с помощью операции записи). Таким образом, полагая, что в памяти системы определены две специальные ячейки ODATA и OSTAT (подобно тому как в предыдущей статье были опреде- лены ячейки с именами INDATA и INSTAT), а также что в ка- честве флажка готовности на этот раз используется второй раз- ряд справа в ячейке OSTAT и состоянию готовности соответст- вует значение 1, можно написать следующий фрагмент програм- мы, где символ, который должен выводиться, находится в ре- гистре X: OWAIT LDA OSTAT ; Получить флажок вывода AND =ОД= % 00000010 ; Выделить второй справа разряд
Команды вывода данных 257 BEQ OWAIT STX ODATA ; Если 0, продолжать проверку : Если 1, вывести символ Как и в случае ввода, сигнал готовности вывода в более слож- ных системах вместо установки соответствующего флажка мо- жет вызывать прерывание. Если в программе, написанной на языке ассемблера, имеется цикл, расположенный внутри другого цикла, который в свою очередь находится внутри еще одного цикла (вспомните цикл ожидания) и т. д., то такую совокупность циклов называют вло- женными циклами (точно так же, как в языках Бейсик и Фор- тран) . Так же как и для ввода, в системе Эпл применяется нестан- дартная схема организации вывода. Символы, выводимые на экран дисплея системы Эпл, предварительно заносятся в опре- деленные фиксированные ячейки памяти. Непосредственное ото- бражение этих символов на экране дисплея реализуется опре- деленной частью системы Эпл, которая полностью недоступна пользователю. Для записи кодов символов в соответствующие фиксированные ячейки памяти на практике используются под- программа COUT и другие подпрограммы такого рода, входя- щие в состав монитора. Упражнения 1. При тех же условиях, которые были приняты в примере команд вывода в конце данной статьи, укажите простейший спо- соб вывода символа, помещенного в регистр А, если регистры X и Y используются в других целях. Запишите соответствующую последовательность команд. (Заметим, что последовательность команд OWAIT LDA AND BEQ STA OSTAT #%00000010 OWAIT ODATA в данном случае использовать нельзя, поскольку с помощью ко- манды STA всегда будет выводиться конечное содержимое ячейки OSTAT, так как именно оно загружается в регистр А посредством команды LDA.) 2. Какова получится длительность интервала ожидания, если значения L, М и N в цикле ожидания, описанном в данной статье, все равны нулю? {Подсказка: здесь обнаруживается прием, который можно широко использовать и в других ситуа- циях. Если вы его не замечаете, проведите внимательно ручную прогонку программы.) 17-589
258 Статья 71 3. Модифицируйте второй пример ввода из предыдущей статьи так, чтобы эти команды осуществляли вывод. Пусть сим- вол, который должен выводиться, помещен .в регистр А. (Заме- тим, что такое, казалось бы, очевидное изменение, как OWAIT LDA ODATA BPL OWAIT STA ODATA здесь будет неработоспособно. Почему?) Задача 3 для решения на ЭВМ Преобразование шестнадцатеричных чисел в 16-разрядные двоичные Напишите программу для ввода четырех шестнадцатерич- ных цифр, преобразования их в 16-разрядное двоичное число, помещаемое в память, и вывода этого числа в десятичном виде с использованием подпрограммы DECOZ (используйте подпро- грамму DECOZ, скопированную .вами при выполнении задачи 1). Ввод данных должен выполняться с помощью подпрограммы GETLNZ. Не забудьте, что в подпрограмме GETLNZ регистры А, X и У используются для специфических внутренних целей. Если какая-нибудь из четырех шестнадцатеричных вводимых цифр имеет код, недопустимый для десятичных цифр (от 0 до 9) или для букв от А до F, то ваша программа должна распечатать сообщение BAD INPUT (ОШИБОЧНЫЙ ВВОД) и предпри- нять попытку нового ввода. После этого следует вывести преобразованное число и вер- нуться на начало программы с целью считывания нового шест- надцатеричного числа. Статья 71 Очереди и метод опроса Более полно возможности, предоставляемые флажками го- товности, проявляются в том, что с их помощью можно обеспе- чить одновременное выполнение операций ввода, обработки и
Очереди и метод опроса 259 вывода. При этом в одно и то же время могут выполняться сле- дующие события: пользователь вводит какие-то символы с кла- виатуры, процессор производит зычисления, а какие-то другие символы отображаются на экране дисплея или выводятся на печатающее устройство. Хвостовая Головная Головная Хвостовая О(т- Q(O) часть часть 0(0) часть часть Рис. 71.1. Очередь символов, реализованная в виде массива. При вводе символов скорость их поступления может ока- заться слишком большой, и процессор не будет успевать их обрабатывать. Поэтому поступающие символы необходимо пред- варительно где-то запоминать, а затем использовать для обра- ботки. Аналогично скорость формирования входных символов может превысить пропускную способность устройства вывода (например, выше 30 симв./с); поэтому и здесь, прежде чем пе- ресылать выходные символы в устройство вывода, их необхо- димо сначала где-то запоминать. Для решения указанных задач используются структуры данных, называемые очередями. К очередям предъявляется требование не изменять упорядо- ченность заносимых в них символов или других элементов дан- ных. Так, первый поступивший в очередь символ должен выби- 17*
260 Статья 71 раться из нее первым. Такая организация данных сокращенно называется «первым пришел — первым вышел», или FIFO (first in — first out). Этим свойством очередь отличается, напри- мер, от стека. В стеке выборка данных всегда осуществляется в обратном порядке (см. статью 60), т. е. первым выбирается эле- мент, пришедший последним. Сокращенно такая организация данных называется «последним пришел — первым вышел», или LIFO (last in — first out)’>. На рис. 71.1 показано, как очередь символов реализуется в виде массива с именем Q. Данный массив имеет длину m и со- держит элементы от Q(0) до Q(m—1). Символы поступают в очередь через хвостовую часть, а покидают очередь через голов- ную часть. (Смысл слова «очередь» можно передать словами «линия ожидания». Мысленно представьте себе людей, стоящих в очереди за бакалейными товарами или билетами; они подстра- иваются в хвост очереди, ожидают обслуживания некоторое время и затем покидают очередь через ее головную часть.) Если очередь на рис. 71.1 рассматривать как входную, то символы (в данном случае сообщение ALL THE WAY AROUND THE RING (ПУТЬ ПО КОЛЬЦУ), вводимые с клавиатуры, по порядку поступают в хвостовую часть очереди, а затем в том же порядке извлекаются из головной части очереди для обработки в процессоре. Если рассматривать очередь как выходную, то со- общение ALL THE WAY AROUND THE RING передается из процессора в хвостовую часть очереди, а затем устройство вы- вода (например, печатающее устройство) в том же порядке отображает эти символы, забирая их из головной части очереди. Данный метод организации работы очереди называется коль- цевым буфером или циклическим списком, поскольку можно считать, что список Q не имеет конца — следующим символом после Q(m—1) является Q(0). (В процессоре 6502 стек также является циклическим списком, так как инкремент значения указателя стека изменяет $FF на нуль, а декремент изменяет нуль на $FF.) Покажем теперь, как реализовать одновременное выполнение операций ввода, обработки и вывода. Сначала будем рассмат- ривать операции обработки, т. е. операции чтения и записи сим- волов посредством подпрограмм ICHAR и OCHAR, которые аналогичны подпрограммам RDKEY и COUT в системе Эпл. У нас имеются две очереди: очередь ввода и очередь вывода. Из примера на рис. 71.1 вытекает, что подпрограмма ICHAR забирает один символ из головной части очереди ввода, а под- *> Существует также аббревиатура GIGO (garbage in — garbage out, «мусор» на входе — «мусор» на выходе) — версия закона Мэрфи для ЭВМ. Если входные данные неверные («мусор»), то получающиеся выходные дан- ные также окажутся «мусором».
Очереди и метод опроса 261 программа OCHAR заносит один символ в хвостовую часть оче- реди вывода. Для одновременного выполнения операций ввода и вывода возможны два способа. Один заключается в использовании пре- рываний. Прерывание возникает при вводе символа через кла- виатуру. В этом случае программа обработки прерывания (на- зовем ее QIN) организует включение нового символа в хвосто- вую часть очереди ввода. Аналогично при выполнении печати символа по прошествии, например, */зо с также возникает преры- вание, а соответствующая программа обработки прерывания (назовем ее QOUT) удаляет один символ из головной части очереди вывода. Для реализации прерываний указанного вида требуются до- полнительные аппаратные средства, что увеличивает стоимость ЭВМ, однако существует другой способ выполнения тех же функций без введения прерываний, с помощью так называемого метода опроса. Этот метод также использует программы QIN и QOUT, но в данном случае они называются программами опроса; эти программы имитируют функционирование аппара- туры прерываний. Программа опроса (назовем ее POLL) является вспомога- тельной программой, которая с высокой частотой (обычно более 1000 раз в секунду) вызывается основной выполняемой програм- мой. Она опрашивает состояние флажков готовности. В этом случае имеются два флажка готовности — один для ввода и один для вывода. Если ни один из флажков готовности не уста- новлен, то программа POLL просто завершается. Если установ- лен флажок готовности ввода, то программа POLL вызывает программу QIN; если установлен флажок готовности вывода, то программа POLL вызывает программу QOUT. Для организации режима опроса важно, чтобы программа POLL вызывалась достаточно часто. При этом нельзя допускать, чтобы между вызовами программы POLL возникали слишком большие промежутки времени (скажем, полсекунды). В против- ном случае вы можете успеть ввести два символа в течение од- ного периода опроса, и первый из этих символов не будет по- ставлен в очередь ввода, поскольку данная операция выполня- ется только программой POLL. Самый простой способ обеспечить работу программы POLL — это сделать так, чтобы она вызывалась из выполняемой про- граммы (и из каждой ее подпрограммы) всякий раз, когда встретится команда с меткой. Данный способ вполне реализуем, поскольку интервал времени между моментами выполнения по- меченных команд всегда невелик (несколько микросекунд). В частности, всякий раз, когда выполняется команда условного или безусловного перехода, происходит переход на метку, а
262 Статья 72 вместе с этим и вызов программы POLL. Заметим, что почти всегда программа POLL работает очень короткий промежуток времени (поскольку она только проверяет два флажка, обнару- живает, что ни один из них не установлен, и завершается). По- этому обращения к программе POLL не вызывают заметного снижения скорости обработки. Упражнения 1. Очередь, как и стек, может оказаться пустой (т. е. не содержать в себе никаких элементов). Определите, какие изме- нения должны быть внесены в программы ICHAR и OCHAR для обработки пустой очереди. 2. На рис. 71.1,а байты информации размещаются в об- ласти очереди от Q (FRONT) до Q(REAR). Однако часто они записываются на участке от Q(FRONT) до Q(REAR—1). Объ- ясните, почему так происходит. (Подсказка: напишите последо- вательность команд для проверки, является ли очередь пустой.) 3. Очереди, так же как и стеки, могут быть перевернуты- ми. Такая очередь выглядит в точности так же, как на рис. 71.1, только eQ(0) становится Q(m), a Q(m—1) становится Q(l). Обоснуйте, почему разумно делать очередь перевернутой. (Под- сказка: рассмотрите вопрос сокращения объема памяти и вре- мени обработки.) Статья 72 Звуковое устройство и программирование в реальном времени В системе Эпл имеется еще одна возможность вывода ин- формации— через звуковое устройство, что предоставляет поль- зователю новые возможности программирования. Как и для всех других средств ввода-вывода в системе Эпл, обращение к зву- ковому устройству происходит по аналогии с обращением к памяти; для этих целей выделена специальная ячейка памяти с адресом $ С030. Поэтому обычно записывают SPKR EQU $ СОЗО
Звуковое устройство и программирование 263 Поскольку за циклов (см. выполниться Однако в отличие от ввода-вывода символов при выполнении любой команды (загрузки, записи и т. д.), обращающейся по адресу SPKR, звуковым устройством будет произведен кратко- временный звуковой сигнал. За счет формирования последова- тельности таких сигналов (импульсов) можно воспроизвести, например, звучание ноты. Покажем, как это сделать. Возьмем, например, ноту ля первой октавы: ее частота со- ставляет 440 Гц. Это значит, что колебание любого объекта с частотой 440 раз в секунду вызовет звучание именно этой ноты. Таким образом, необходимо сделать так, чтобы звуковое уст- ройство Эпл выдавало сигнал 440 раз в секунду. Это можно реализовать следующим способом: 1) выдать импульс; 2) ожидать в течение приблизительно */44ос; 3) перейти на шаг 1. Сколько машинных циклов заключено в V440C? одну секунду выполняется 1 023 000 машинных статью 36), за указанное время должно 1 023 000/440 или 2325 циклов. Но так как импульс формируется за четыре цикла (если используется команда загрузки), а шаг 3 требует трех циклов, то для шага 2 остается 2318 машинных циклов. Теперь надо написать подпрограмму, как это делалось в при- мере с циклами ожидания в статье 70, которая бы обеспечивала задержку на 2318 машинных циклов. Один из способов построе- ния такой программы состоит в следующем. 1) Разделим 2318 на 256, получится число 9 и некоторая дробная часть. Следовательно, цикл, который будет выполнять- ся немногим меньше, чем 256 раз, обеспечит ожидание в течение 10 мкс. 2) Разделим 2318 на 10, получится 231 и в остатке 8. Следо- вательно, в регистр X надо загрузить число 231, на что потре- буется два машинных цикла, и использовать в программном цикле 10 машинных циклов. Это можно реализовать, например, с помощью последовательности команд LDA-BEQ-DEX- BNE. Весь цикл надо повторить 231 раз. Итого получим 2310 ма- шинных циклов. (На самом деле будет 2309 машинных циклов, ввиду того что при выполнении команды BNE в программном цикле в последний раз потребуется только два машинных цик- ла, а не три, поскольку в последний раз переход по условию выполняться не будет.) 3) В результате будет получено 2311 машинных циклов. Ко- манда BEQ (для нее потребуется три машинных цикла) вместе с двумя командами NOP, на каждую из которых будет израс- ходовано по два цикла, дополнит число циклов до 2318. Таким
264 Статья 72 образом, полный программный цикл будет иметь следующий вид: А440 LDA LDX SPKR #1231 ; Выдать сигнал ; Начало цикла ожидания A440L LDA #!0 ; Ожидать 10 машинных ; циклов BEQ А440С ; (2-J-3+ А440С DEX ; +2 + 3) машинных циклов и BNE A440L ; вернуться к началу цикла BEQ A440N ; Использовать еще 7 A440N NOP NOP ; машинных циклов за счет ; команд ; BEQ —NOP —NOP JMP А440 ; Перейти к началу цикла Очевидно, что данный цикл бесконечен. Если требуется, чтобы отдельная музыкальная нота звучала в течение конечного про- межутка времени, необходимо включить соответствующие до- полнительные операторы. На выполнение этих операторов за- трачивается определенное время, поэтому должен быть выпол- нен перерасчет некоторых констант, таких, как 231 *). Рассмотренная выше программа является примером про- граммирования в реальном времени, когда нашей целью явля- ются не вычисления, а выполнение некоторых действий в опре- деленный момент времени. Другим типичным примером из этой области, хотя это средство и не предусмотрено в системе Эпл, является вывод цифр на семисегментный индикатор. Такой ин- дикатор состоит из семи электролюминесцентных диодов (ЭЛД), с помощью которых (включая одни диоды и выключая другие) можно отображать цифры от 0 до 9, как это показано на рис. 72.1. Затушеванные участки соответствуют включенным диодам, незатушеванные — выключенным. Каждый диод вклю- чается при обращении к определенной ячейке подобно тому, как запускается звуковое устройство. Включенный ЭЛД не остается в таком состоянии, он начинает гаснуть и теряет свою яркость в течение приблизиттельно 1мс (1000 мкс). Следовательно, так же как и в случае воспроизведения звука, ЭЛД надо включать периодически. Другими словами, наша программа по крайней В системе Эпл есть подпрограмма BELLI (определяемая псевдокоман- дой BELLI EQU $FBD9), которая на основе рассмотренного метода фор’ мирует гудок длительностью 0,1 с. Это средство целесообразно использовать в качестве простого сигнализатора системы Эпл для пользователя.
Звуковое устройство и программирование 266 мере каждые 1000 мкс должна обращаться к специальной ячей- ке, относящейся к ЭЛД. Семисегментные индикаторы находят применение в элек- тронных часах. В большинстве случаев используются индикаторы Рис. 72.1. на жидких кристаллах, обладающие лучшими характеристиками по сравнению с ЭЛД, но и в этом случае индикация строится по семисегментному принципу. Упражнения 1. Громкость звука можно увеличить, если заставить зву- ковое устройство дать два импульса подряд (т. е. дважды ис- пользовать команду LDA SPKR),a затем, как и раньше, перейти (а) (Ь) (₽) Рис. 72.2.
266 Статья 73 на ожидание. Измените программу А440 так, чтобы решить эту задачу, и при этом программный цикл, как и прежде, требовал бы в точности 2325 машинных циклов. 2. Измените программу А440, чтобы она воспроизводила звук «до» первой октавы (частотой 512 Гц). Можно ли решить эту задачу только за счет изменения константы, записываемой в ре- гистр X, и изменения команд, стоящих после программного цик- ла? Заметим, что число 1 023 000 не делится нацело на число \_______________________ а Ь с d е f д 0 Рис. 72.3. 512, поэтому необходимо будет воспользоваться округленным значением. 3. Пусть семисегментный индикатор имеет буквенные обо- значения, показанные на рис. 72.2. Предположим, что написана подпрограмма, которая получает некоторую величину через ре- гистр А в формате, указанном на рис. 72.3. Подпрограмма будет проверять каждый из этих битов и включать соответствующий ЭЛД, если данный бит имеет значение 1. Например, цифра 0 образуется ЭЛД, помеченными буквами a, b, с, е, f, g (рис. 72.2), и, следовательно, подпрограмма через регистр А в двоичном виде получит значение ИЮНЮ или в шестнадцатеричном — ЕЕ. Для остальных цифр от 1 до 9 приведите в шестнадцатерич- ном виде значения содержимого регистра А. Статья 73 Дополнительные средства описания строк В статье 26 мы рассмотрели оператор описания ASC, позво- ляющий определить символьную строку в формате ASCII, для которого в коде каждого символа самый старший бит равен еди- нице (если строка заключена в двойные кавычки). Однако транслятором LISA предусмотрены средства описания строк, ис- пользующие другие псевдокоманды.
Дополнительные средства описания строк 267 С помощью псевдокоманды INV порождается символьная строка, которая в системе Эпл может быть отображена на экра- не дисплея в так называемом инверсном режиме, т. е. в виде черных символов на белом поле экрана в противоположность обычному способу отображения белыми символами на черном несветящемся экране. Такой режим широко применяется, на- пример, в весьма популярной программе VISICALC, входящей в систему Эпл. Запись INV "ENTER THE FIRST NUMBER" порождает строку ENTER THE FIRST NUMBER, состоящую из 22 символов, причем первые два бита кода каждого символа равны 00, что в системе Эпл интерпретируется как инверсный режим (см. табл. П9 и П10). Вызов подпрограммы монитора системы Эпл с помощью ко- манды JSR SETINV (set inverse mode — установить инверсный режим) обеспечивает другой способ отображения символов в инверсном режиме. После вызова подпрограммы SETINV любой символ, даже если первые два бита в его коде не равны нулю, вызовом JSR COUT будет отображаться в инверсном виде. Возврат к обычному режиму отображения в системе Эпл осу- ществляется с помощью вызова JSR SETNRM (set normal mode — установить обычный режим). Определение SETINV и SETNRM имеет следующий вид: SETINV EQU $FE80 SETNRM EQU $FE84 Псевдокоманда BLK порождает символьную строку, которая в системе Эпл на экране дисплея может быть отображена в так называемом режиме мерцания, или режиме мелькания, что представляет собой чередование обычного и инверсного режимов. (В режиме мерцания курсор на экране дисплея представляется пустым местом). Запись BLK "ENTER THE FIRST NUMBER" порождает символьную строку ENTER THE FIRST NUMBER, причем первые два бита кода каждого символа равны 01, что в системе Эпл соответствует режиму мерцания (см. табл. П9 и ШО). Многие программисты предпочитают определять строки та- ким образом, чтобы значение длины строки вытекало из ее вида. На этот случай транслятором LISA предусмотрены две псевдо- команды— DCI и STR. Псевдокоманда DCI (define character immediate — непосредственное определение символьной строки) описывает строку, в которой левый бит кода последнего символа
268 Статья 73 отличается от левых битов кодов всех остальных символов дан- ной строки. Таким образом, можно написать: LDX $FF LOOK INX LDA MSG4, X ORA # % 10000000 JSR COUT LDA MSG4, X BMI LOOK ; Начальное значение X= — l ; Сместиться к следующему символу ; строки и загрузить его ; Установить левый бит в 1 ; и отобразить символ ; Если левый бит был равен 1, то в ; строке есть еще символы — ; выбрать их если было объявлено COUT EQU $FDED, a MSG4 определено как MSG4 DCI "ENTER THE FIRST NUMBER" Тогда на дисплее будет отображена строка ENTER THE FIRST NUMBER. Заметим, что если строка ENTER THE FIRST NUMBER дана в одиночных кавычках, то левый бит кода по- следнего символа будет равен 1, а не 0. Псевдокоманда STR (string — строка) порождает строку, ко- торой предшествует байт, содержащий значение длины этой строки. При использовании транслятора LISA 1.5 левый бит указанного байта совпадает с соответствующими битами кодов символов оставшейся части строки и, в частности, равен 1, если строка заключена в двойные кавычки. Поэтому может быть на- писана такая последовательность команд: LDA MSG7 ; Получить байт длины ; и установить его AND ; левый разряд в нуль TAX ; Длина строки теперь в X LDY #10 ; (первый символ —в ячейке ; MSG7-H1) WATCH INY ; Сместиться к следующему символу LDA MSG7, Y ; строки и загрузить его JSP COUT ; Отобразить этот символ DEX ; Проделать это :N раз, BNE WATCH ; где N — длина строки где объявлено COUT EQU $FDED, a MSG7 для транслятора LISA 1.5 определяется как MSG7 STR "ENTER THE FIRST NUMBER"
Дополнительные средства описания строк 269 Тогда вновь будет отображена строка ENTER THE FIRST NUMBER (обратим внимание на необходимость применения в данном случае команды AND). В системе LISA 2.5 в байте дли- ны используются все восемь разрядов, и первые три команды в вышеприведенном примере могут быть заменены одной командой LDX MSG7. В заключение опишем псевдокоманду HEX, которая позво- ляет определить в строке отдельные шестнадцатеричные цифры. Следовательно, если записать MSG1 HEX C5CED4C5D2A0D4C8C5A0 HEX C6C9D2D3D4A0CED5CDC2C5D2 то, как и раньше, получим строку ENTER THE FIRST NUMBER (см. рис. 24.1 .в статье 24). Тем не менее основное назначение псевдокоманды HEX связано с определением шестнадцатерич- ных цифр, которым в обычном режиме не соответствуют уста- новленные коды символов. Заметим, что с псевдокомандой HEX не используются кавычки и знак доллара. Упражнения 1. В каждом примере за счет однократного применения псев- докоманды INV или BLK получите результат, эквивалентный данному. * а) BYT $4B BYT $79 * б) BYT $0B BYT $39 2. В каждом примере за счет однократного применения псев- докоманды DCI или STR получите результат, эквивалентный данному. а) BYT $D3 BYT $D4 BYT $CF BYT $50 б) BYT $83 BYT $D9 BYT $C5 BYT $D3 3. За счет однократного применения псевдокоманды HEX получите результат, эквивалентный данному. (Заметим, что при
270 Статья 74 этом определяются только три байта, а не пять. См. статью 63.) BYT !10 BYT $3456 HBY $3456 Статья 74 Команды для нулевой страницы Представим себе память процессора 6502 в виде книги; при этом можно считать ее состоящей из 256 частей, которые будем называть страницами, а каждая страница содержит по 256 по- зиций для символов (или байтов). В шестнадцатеричном виде страницы можно пронумеровать от 00 до FF. Страница с номе- ром ab содержит байты (или поля), которым соответствуют ад- реса от ab 00 до a&FF. Пользуясь такими обозначениями, можно отметить, что стра- ница с номером 1 отведена под стек, поскольку стеку соответст- вуют адреса от 0100 до 01FF (см. статью 60). Страница с но- мером 2 выделена для буфера INBUF, используемого подпро- граммами GETLN и GETLNZ (см. статью 25). Наиболее важной, однако, является страница с нулевым но- мером. Адреса ячеек, входящих в эту страницу, имеют длину» равную восьми разрядам (от 00 до FF), и предусмотрен целый ряд команд, использующих 8-разрядные (т. е. однобайтовые) адреса, а не 16-разрядные. Эти команды называются команда- ми для нулевой страницы. Почти все команды процессора 6502, рассчитанные для ра- боты с 16-разрядными адресами, имеют соответствующие ана- логи и для 8-разрядных адресов (т. е. для нулевой страницы). Но вместе с этим существует четыре исключения: 1) Во многих случаях команды для нулевой страницы допу- скают косвенную адресацию; об этом речь пойдет в двух сле- дующих статьях. 2) Кроме режима косвенной адресации, индексирование с помощью регистра Y в командах для нулевой страницы нельзя
Команды для нулевой страницы 271 использовать (за исключением команд LDX и STX). Это неболь- шая потеря, поскольку .в таких случаях всегда можно воспользо- ваться командами с 16-разрядной адресацией (к тому же ну- левая страница — это не слишком удачное место для хранения массивов). 3) Если Z — адрес на нулевой странице, то можно приме- нять команды STX Z, Y и STY Z, X. (Ранее было показано, что в других случаях эти команды не могут быть использованы.) 4) С помощью команд JMP и JSR может быть выполнен переход только по 16-разрядным адресам. (И опять потери от этого невелики, поскольку любой адрес на нулевой странице может быть представлен в шестнадцатиразрядном виде.) Самым важным достоинством нулевой страницы является то, что при ее использовании экономится как время, так и объем памяти, это можно .видеть из табл. П4, где Z обозначает команду для нулевой страницы. Так, например, для выполнения команды ADC Q затрачивается 4 машинных цикла и требуется 3 байта памяти, тогда как для команды ADC Z (аналога команды ADC Q, соответствующего нулевой странице) требуется только 3 цикла и 2 байта памяти. Такие же соотношения справедливы для команд LDA, STA и многих других. Возвратимся к примерам из статьи 33. Так, в первой про- грамме за счет размещения ячейки TEMP на нулевой странице можно сэкономить два байта памяти, а время выполнения про- граммы уменьшить на два машинных цикла (конкретно, по од- ному байту и по одному циклу для каждой из команд STA и ADC). Аналогично во второй программе из статьи 33 можно сэкономить 2 байта памяти, а время счета сократить на 2п цик- лов, где п — количество цифр в числе, которое вводится. Заме- тим, что во втором случае программный цикл выполняется п раз и для каждого раза экономится два машинных цикла, но объем требуемой памяти при этом сокращается только на два байта (это объясняется тем, что в тексте данной программы команды STA TEMP и ADC TEMP встречаются только по одному разу). В системе Эпл нулевая страница почти целиком использует- ся для управляющих и общесистемных функций, поскольку это позволяет существенно увеличивать быстродействие и экономить память. При построении каких-либо систем на базе процессора €502 рекомендуется поступать так же. Однако, в системе Эпл нулевую страницу нельзя использовать произвольным образом; в противном случае может произойти наложение вашей про- граммы на данные на нулевой странице, используемые монито- ром Эпл или системами LISA и Бейсик. Если это произойдет, то при использовании в дальнейшем монитора Эпл, систем LISA и Бейсик или таких подпрограмм, как RDKEY или COUT,
272 Статья 74 работа указанных программных средств может оказаться совер- шенно непредсказуемой и неверной. К счастью, ячейки памяти с адресами от 19 до 1F не ис- пользуются ни монитором Эпл, ни системами LISA и Бейсик. Поэтому в указанные ячейки можно записывать переменные, как это делалось с переменной TEMP в приведенном выше при- мере. Для определения переменных в этом случае может ис- пользоваться аналог псевдокоманды EQU (см. статью 25), обо- значаемый EPZ (Equate to page zero — отождествить с нулевой страницей). Тогда, записывая в программе последовательность определений: TEMP EPZ $ IF ТЕМР2 EPZ $1E TEMP3 EPZ $1D вы можете использовать обозначения TEMP, ТЕМР2 и ТЕМРЗ как адреса на нулевой странице, поэтому адресация в такой ко- манде, как, например, STA ТЕМР2, будет 8-разрядной. (Если же определение переменной производится посредством псевдо- команды EQU, а не EPZ, то адрес в команде STA ТЕМР2 будет 16-разрядным, несмотря на то что один из байтов этого адреса будет нулевым.) В системе Эпл многие ячейки нулевой страницы имеют спе- циальное назначение и доступны пользователю. Например, ячей- ка с адресом $33 предназначена для символа-подсказки, т. е. для символа, который высвечивается на экране дисплея, когда система ожидает от вас ввода через клавиатуру. Ассем- блер LISA хранит «!» в ячейке с адресом $33, поэтому симво- лом-подсказкой ассемблера LISA является именно этот знак. При разработке вашей собственной системы в качестве символа- подсказки можно использовать другой символ (например, знак доллара $), важно только, чтобы этот символ в начале работы системы загружался в ячейку с адресом $33. Подпрограмма GETLN, входящая в состав системы Эпл и рассмотренная в статье 25 (упр. 3), выводит на экран дисплея символ-подсказку, а затем вызывает другую системную подпро- грамму GETLN1, которую можно использовать (как и GETLN) для ввода символьной строки, но без выдачи символа-подсказки. Таким образом, команда JSR GETLN эквивалентна последова- тельности команд LDA PROMPT JSR COUT JSR GETLN1
Команды для нулевой страницы 273- при этом надо использовать определения GETLN 1 EQU $FD6F и PROMPT EPZ $33. Индексирование, выполняющееся для нулевой страницы, не выводит адреса за пределы этой страницы. Так, например, не- смотря на то что в шестнадцатеричном виде А0+А0=140, с по- мощью команды для нулевой страницы LDA $А0,Х осуществля- ется загрузка регистра А содержимым ячейки с адресом 0040 (не 0140), если при этом в регистре X было записано шестнад- цатеричное число АО. Упражнения 1. >|с а) Сколько машинных циклов (максимум и минимум) и байтов памяти будет сэкономлено, если в подпрограмме MULT, приведенной в статье 39, ячейки MDATA1 и MDATA2 разместить на нулевой странице? Проверьте работу полученной подпрограммы. (Для анализа старого и нового вариантов не просматривайте подпрограммы целиком; обратите внимание только на те команды, которыми отличаются эти два варианта. Не забудьте, что если время однократного выполнения про- граммного цикла сокращается на k машинных циклов, а сам программный цикл повторяется п раз, то общий выигрыш в бы- стродействии равен kn машинным циклам.) б) Сколько машинных циклов (максимум и минимум) и байтов памяти будет сэкономлено, если в подпрограмме DIV, приведенной в статье 40, ячейки DDATA1 и DDATA2 разместить на нулевой странице? Проверьте работу полученной подпрограм- мы. (Для сравнительного анализа старого и нового вариантов подпрограммы учтите замечания, сделанные в п. а.) 2. Можно ли добиться сокращения количества машинных циклов или байтов памяти за счет того, что некоторые команды этой программы поместить на нулевой странице? Почему можно- или почему нельзя? 3. В статье 59 было показано, что если содержимое указателя стека с помощью команды TSX занести в регистр X, то в резуль- тате выполнения команды LDA $0101,Х в регистр А будет за- гружено содержимое верхушки стека. Отсюда следует, что с помощью команды STA $00FF,X осуществляется запись содер- жимого регистра А в ячейку, которая в данный момент нахо- дится за пределами стека (выше верхушки стека). Однако ука- занная команда STA не может быть использована в качестве команды для нулевой страницы. Почему? 18—589
274 Статья 75 Статья 75 Предындексная косвенная адресация В статье 58 были рассмотрены команды косвенного перехода, такие, как JMP(B); они используют адрес, который сам хранит- ся в памяти. Это частный случай косвенной адресации, которая широко используется в большинстве ЭВМ, больших и малых. Основную идею этого понятия поясним следующими приме- рами. С помощью команды «загрузка Z», использующей косвен- ную адресацию, осуществляется загрузка величины R в регистр, при этом Z содержит адрес величины R. Посредством команды «запись Z», также использующей косвенную адресацию, содержи- мое регистра записывается в ячейку R, при этом Z, как и прежде, содержит адрес ячейки R. В общем для какой-нибудь команды можно одновременно использовать как косвенную адресацию, так и индексирование. Однако при этом возникает вопрос: какой из этих механизмов должен выполняться первым? Если первым выполняется индек- сирование, то посредством команды вида «загрузка Z, индекс», использующей косвенную адресацию, будет производиться за- грузка величины R, причем адрес величины R будет определять- ся такой командой с использованием обычной прямой адресации. С другой стороны, если сначала выполняется косвенная ад- ресация, то действие команды вида «загрузка Z, индекс», ис- пользующей косвенную адресацию, будет аналогично действию команды вида «загрузка R, индекс», где адрес величины R будет размещен в Z. Процессор 6502 необычен тем, что он допускает использова- ние обоих типов косвенной адресации, хотя и с определенными ограничениями: 1) Все косвенные адреса должны быть записаны на нулевой странице. Поэтому механизм косвенной адресации более поле- зен для реализации системного программного обеспечения Эпл, которое использует почти всю нулевую страницу, а не пользо- вательских программ. 2) В предындексной косвенной адресации (когда сначала выполняется индексирование) используется только регистр X, а в постиндексной адресации (когда сначала производится кос- венная адресация) — только регистр Y. 3) За исключением команды JMP, механизм косвенной адре- сации без индексирования не может быть использован вовсе.
Предындексная косвенная адресация 275 Можно либо очистить регистр X или Y (аналогично тому, как сбрасывался флажок переноса перед выполнением команды ADC с тем, чтобы выполнить сложение без переноса), либо применить другой прием, который будет описан в конце следующей статьи. Команда же JMP представляет собой исключение из вышепри- веденного правила (1). 4) Механизм косвенной адресации с индексированием может быть применен только для группы команд, рассмотренной в статье 52, а именно LDA, STA, ADC, SBC, СМР, AND, ORA и EOR. Для предындексной косвенной адресации используется обо- значение (Z, X). Тогда по команде LDA (ALPHA, X) в регистр А будет загружаться содержимое ячейки R, причем командой LDA ALPHA,X в регистр А была бы загружена младшая поло- вина адреса ячейки R, а командой LDA ALPHA-}-! 1,Х— старшая половина того же адреса. При тех же условиях по команде STA (ALPHA,X) содержи- мое регистра А будет записано в ячейку R; по команде СМР (ALPHA,X) будет выполнено сравнение содержимого ре- гистра А и величины R, а также соответствующая установка флажков состояния и т. д. Механизм предындексной косвенной адресации в сильной мере зависит от того, как используется нулевая страница. При- чина этого в том, что для применения данного механизма тре- буется несколько адресов, все на нулевой странице, индексиро- вание для которых может быть выполнено с помощью реги- стра X. Наилучшим образом достоинства предындексной косвенной адресации проявляются при реализации вызова подпрограмм с параметрами. Допустим, например, что имеется подпрограмма LOGAND (I, J, К), написанная на языке Фортран, которая при- сваивает переменной К результат операции логического И пере- менных I и J. На языке Фортран можно записать CALL LOGAND (Ml, М2, М3) и в результате работы подпрограммы переменная М3 получит значение операции логического И пере- менных Ml и М21). В языке ассемблера, если Ml, М2 и М3 представляют однобайтовые данные, перед командой JSR LOGAND должна быть выполнена команда LDX #MSEQ, при этом на нулевой странице необходимо определить следующие поля (псевдокоманда ADR описана в статье 63): MSEQ ADR Ml ADR М2 ADR М3 i) Эта возможность языка Фортран отсутствует в языке Бейсик. 18*
276 Статья 75 Подпрограмма LOGAND тогда имеет вид LOGAND LDA (О, X) ; LOGAND (I, J, К) получает ; значение I, AND (2, X) ; выполняет логическое И с J, STA (4, X) ; заносит результат в К RTS ; и завершает работу Выполнение команды AND(2,X) здесь начинается с прибав- ления числа 2 к 8-разрядному адресу MSEQ (который находится в регистре X), в результате получается адрес MSEQ+I2; однако адрес величины М2 находится в ячейках с адресами MSEQ+I2 и MSEQ+13, поэтому фактически выполняется команда AND М2. Последовательность адресов, таких, как в поле памяти с име- нем MSEQ в приведенном выше примере, называется вызываю- щей последовательностью. Через какое-то время в программе в регистр X можно опять загрузить некоторый адрес нулевой стра- ницы, но уже соответствующий другой вызывающей последова- тельности, и это позволит выполнить вызов, скажем, такой под- программы: LOGAND (М4, М5, Мб). Правда, для того чтобы эффективно применять данный метод в процессоре 6502, необ- ходимо иметь большой объем доступной памяти на нулевой странице;, к сожалению, такую память в нужном, объеме можно получить только в том случае, если на базе процессора 6502 раз- рабатывается какая-то другая система, не Эпл. Механизм предындексной косвенной адресации удобно при- менять также в следующих случаях. Пусть на нулевой странице памяти системы хранится несколько адресов, не обязательно в виде массива. Тогда к данным, определяемым любым из этих адресов, можно обращаться, если в регистр X загрузить соот- ветствующий адрес на нулевой странице и затем воспользоваться нужной командой, например LDA (0,Х) (или STA, ADC и т. д.). Индексирование, выполняемое в процессе предындексной ко- свенной адресации, не выводит адреса за пределы нулевой стра- ницы так же, как это было в предыдущей статье. Так, например, если регистр X содержит значение $А0, то в команде LDA ($А0,Х) используется косвенный адрес, записанный в ячейках $40 и $41, но не в ячейках $0140 и $0141. Упражнения 1. Пусть Z соответствует адресу на нулевой странице А1, а в регистре X содержится число 4. По каким адресам должны быть записаны значения соответственно 09 и 14, чтобы с по- мощью команды STA (Z,X) содержимое регистра А можно было запомнить в ячейке с адресом 0914?
Постиндексная косвенная адресация 277 >|с 2. Напишите подпрограмму EXCH (Bl, В2), в которой выполнялся бы обмен содержимого байтов, обозначенных име- нами В1 и В2. Пусть при этом адреса В1 и В2 записаны на нулевой странице по адресам CS, CS-|-!1, CS-j-12 и CS+13, причем для вызова подпрограммы EXCH (Bl, В2) команда JSR ЕХСН следует за командой LDX 4I=CS. Для обращения к В1 и В2 используйте предындексную косвенную адресацию. Ре- гистр Y не используйте. 3. Допустим, вы пишите подпрограмму ADD 16 (QI, Q2, SUM), в которой выполняется сложение 16-разрядных чисел Q1 и Q2 и получается 16-разрядное значение суммы SUM. Вам хотелось бы написать эту подпрограмму по аналогии с про- граммой предыдущего упражнения. Тогда после команды LDX #CS будет записана команда JSR ADD16, посредством ко- торой будет выполнен вызов подпрограммы ADD16 (QI, Q2, SUM), причем адреса переменных I, J и К будут записаны на нулевой странице в ячейках от CS до CS4-15. Какие трудности возникнут при написании такой подпрограммы? (Подсказка: попробуйте сами написать эту подпрограмму и увидите.) Статья 76 Постиндексная косвенная адресация Для постиндексной косвенной адресации используется обо- значение вида (Z), Y. Тогда команда LDA(Z), Y будет анало- гична команде LDA R.Y, где адрес величины R с переставлен- ными байтами хранится в ячейках нулевой страницы с адреса- ми Z и Z-f-ll. При тех же условиях команда STA (Z), Y анало- гична команде STA R, Y, команда ADC (Z), Y аналогична команде ADC R, Y и т. д. Для того чтобы легче было запомнить различие в записях (Z, X) и (Z), Y, можно дать следующую интерпретацию: в слу- чае (Z, X) сначала выполняется индексирование (вычисляется Z, X), а затем — косвенная адресация (вычисляется (Z, X); в случае (Z), Y сначала выполняется косвенная адресация (вы- числяется (Z)), а затем — индексирование (вычисляется (Z), Y).
278 Статья 76 Постиндексную косвенную адресацию можно применять в тех случаях, когда подпрограмма имеет параметр, являющийся име- нем массива, такого, как символьная строка. Пусть, например,. DISPL(S) является подпрограммой, с помощью которой на эк- ране дисплея отображается строка S. Мы отводим на нулевой странице место (назовем его ADRS) для адреса S. Тогда, для того чтобы вывести на экран дисплея строку, например, с име- нем Т, сначала необходимо по адресу ADRS записать адрес строки Т: LDA STA LDA STA ; Переслать адрес Т ADRS ; по адресу ADRS /Т ; (сначала младшее полуслово адреса, ADRS-j-ll ; затем — старшее) (Обозначения и /Т описываются в статье 58.) В качестве подпрограммы DISPL может быть использована первая под- программа из статьи 25, .в которой вместо команды LDA Т, Y выполняется команда LDA (ADRS), Y (с тем же результатом). В общем запись (ADRS), Y вместо Т, Y применяется для обо- значения индексированной ссылки на массив, имя которого яв- ляется параметром подпрограммы. Для реализации описанного метода требуются только две ячейки на нулевой странице; поэтому данный способ использу- ется чаще, чем соответствующий способ с предындексной косвен- ной адресацией, для которой требуется много ячеек на нулевой странице. Постиндексную косвенную адресацию целесообразно приме- нять для обработки списков процессором 6502. Узел L списка длиной п представляет собой совокупность п байтов L, L-f-ll и т. д. до L-j-ln—!1. Для указателя на узел L списка отводится два байта (скажем, L2 и L2-|-!l), в которые записывается адрес узла L. Простому списку длины k соответствуют k узлов Li,...»Lk списка, каждый из которых занимает два байта (на- пример, Li и Li+!1) и содержит указатель на следующий узел или нуль, если этот узел является последним в списке. (Нулевое значение не может быть адресом никакого узла списка.) Пусть в ячейках PTR и PTR-J-H нулевой страницы содер- жится указатель на узел Li некоторого списка (будем говорить, что эти ячейки указывают на узел Li). Тогда посредством команд LDY #!3 LDA (PTR), Y будет осуществляться загрузка регистра А содержимым байта с номером 3 узла Li (заметим, что первый байт узла Li имеет
Постиндексная косвенная адресация 279 нулевой номер). Если, как это отмечалось выше, в первых двух байтах каждого узла содержится указатель на следующий узел, то после .выполнения следующего фрагмента программы ячейки PTR и PTR4-11 будут указывать на следующий по порядку узел: LDY #!0 ; Подготовить регистр Y для загрузки LDA (PTR), Y ; младшего полуслова указателя РНА ; Сохранить младшее полуслово ; указателя INY ; Увеличить содержимое регистра Y для LDA (PTR), Y ; загрузки старшего полуслова ; указателя STA PTR4-I1 ; Записать в PTR (старшее полуслово) PLA ; Восстановить младшее полуслово STA PTR ; указателя и записать его в PTR Оба способа косвенной адресации можно использовать, пред- варительно очистив соответствующий регистр (X или Y), что эквивалентно косвенной адресации без индексирования. В этом случае постиндексная адресация осуществляется несколько бы- стрее (за пять машинных циклов вместо шести). Однако лучше просто использовать любой индексный регистр, который ока- жется свободным, а если оба регистра незаняты, то использо- вать регистр Y. В некоторых случаях может быть известно, что содержимое регистра X (или Y) равно нулю, как это бывает, например, по окончании цикла, в котором использовались ко- манды DEX и BNE. Косвенная адресация без индексирования может быть выпол- нена еще быстрее, если воспользоваться следующим приемом. Пусть L и L-J-11 обозначают две ячейки на нулевой странице. Сделаем так, чтобы в ячейке L было постоянно записано нуле- вое значение. Тогда, для того чтобы посредством косвенной ад- ресации обратиться к ячейке Р, имеющей в шестнадцатеричном представлении адрес abed, запишем ab в ячейку L-j-Il, а в ре- гистр Y загрузим константу cd. После таких операций в ячей- ках L и L+!l будет записана 2-байтовая величина а&00, и по команде LDA (L), Y будет выполняться загрузка регистра А значением переменной, адрес которой равен abOO+cd или abed. Другими словами, по команде LDA (L), Y в регистр А будет загружаться величина Р; аналогично по команде STA (L), Y содержимое регистра А записывается в ячейку Р и т. д. Описан- ные выше действия, а именно запись ab в ячейку L-f-H и за- грузка регистра Y величиной cd, могут быть выполнены с по- мощью следующих команд:
280 Статья 76 LDY IP STY L4-!l LDY #P Упражнения # 1. Пусть в ячейках Z и Z-|-!l нулевой страницы содер- жатся величины соответственно 9 и 8. Что нужно загрузить в регистр Y, для того чтобы по команде STA (Z), Y можно было содержимое регистра А записать в ячейку с адресом 0908? (Будьте внимательны! В данной задаче имеется некоторая тон- кость.) 2. Напишите подпрограмму MSGOUT для отображения не- которого сообщения на экране по одному символу за раз (ис- пользуя подпрограмму COUT из статьи 25). Считайте, что адрес отображаемого сообщения находится в ячейках MSG и MSG-j-ll на нулевой странице. Сообщение состоит из байта длины, за ко- торым следует непосредственно та строка, которая должна ото- бражаться; т. е. если в ячейках MSG и MSG-j-ll записано 16-разрядное число а, то в ячейках с адресами от а+!1 до a-f-!« содержится «-байтовая строка сообщения, а в ячейке с адресом а — само число «. >]с 3. Допустим, что в последней программе данной статьи мы хотели исключить команды РНА и PLA и написали следую- щую программу: LDY #10 LDA (PTR), Y INY STA PTR LDA (PTR), Y STA PTR-H1 Однако, эта программа работать не будет. Почему?
Длинные массивы 281 Статья 77 Длинные массивы Поскольку регистр X, так же как и регистр Y, всего лишь 8-разрядный, то за счет индексирования представляется воз- можным обращаться не более чем к 256 байт произвольного массива, как это показано в статье 13. Массивы, длина которых превышает 256 байт, для процессора 6502 называются длинны- ми массивами. Покажем далее, как реализовать индексирование для таких массивов. Пусть для обращения к элементу T(J) массива сначала вы- полняется команда LDX J, а затем LDA Т, X. При выполнении команды LDA Т, X происходит прибавление 16-разрядного адре- са массива Т к 8-разрядной величине J. При обращении к длинному массиву переменная J будет иметь 16-разрядное представление. Нам необходимо прибавить её значение к 16-разрядному адресу начала массива Т посредством коман- ды сложения 16-разрядных операндов (см. статью 15). В ре- зультате этого будет получен адрес элемента T(J) массива; теперь, как и в предыдущем разделе, можно воспользоваться постиндексной косвенной адресацией: LDA #Т Прибавить адрес массива T CLC (вначале бит переноса очищен) ADC J к 16-разрядной переменной J STA ZP (байты переставлены), LDA /Т а результат поместить ADC J-Hl в ячейки ZP и ZP-f-ll STA ZP-H1 нулевой страницы LDY #!0 Далее воспользоваться постиндексной LDA (ZP), Y косвенной адресацией (при Y=0) Данную программу можно несколько улучшить, если вос- пользоваться одним из приемов, описанных в предыдущем раз- деле. Для этого команда LDY ф!0 исключается, вместо команды STA ZP используется команда TAY, а в ячейке ZP постоянно содержится нулевое значение. Тогда, как это объяснялось ранее, если abed является шестнадцатеричным адресом элемента T(J) массива, то в ячейках ZP и ZP-H1 окажется записанной вели- чина айОО (с переставленными байтами), в регистр Y будет за- гружено значение cd, и по команде LDA (ZP), Y в регистр А будет загружен байт с адресом abW-\-cd=abcd.
282 Статья 77 Пусть теперь мы хотим увеличить значение индекса в длин- ном массиве. Увеличить просто содержимое регистра Y нельзя; необходимо увеличивать 16-разрядное значение, содержащееся в регистре Y и ячейке ZP-J-I1 (если при этом используется та усовершенствование, которое было описано в предыдущем аб- заце); аналогичный прием должен применяться и для уменьше- ния значения индекса. Указанные действия с использованием методов из статьи 30 будут выполняться посредством таких по- следовательностей команд: INY TYA BNE BETA BNE ВЕТА INC ZP+ll DEC ZP4-I1 ВЕТА (следующая команда) BETA DEY Увеличение индекса Уменьшение индекса Заметим, что здесь мы опять используем то обстоятельство, что команды пересылок, такие, как TYA, устанавливают флажок нуля (и знака). Из этого правила имеется лишь одно исклю- чение: команда TXS совсем не влияет на состояние флажков. Индексы-константы в длинных массивах используются точно так же, как и в коротких массивах. Если определен массив Т длины 10 000, причем его первым элементом является Т(0), то для загрузки в регистр Y элемента Т(5000) можно использовать команду LDY T-J-I5000 точно так же, как команду LDY Т+!50 в случае короткого массива для загрузки в регистр Y элемента Т(50). Могут использоваться также так называемые параллельные и последовательные длинные массивы. Для обычного последова- тельного массива 2-байтовых величин значение индекса умно- жается на 2, как это показано в статье 32; в последовательном длинном массиве используется операция сдвига 16-разрядных операндов (см. статью 34). Заметим, что каждый из четырех параллельных массивов длиной, например, 256 байтов является коротким массивом, и их обработка может вестись отдельно, несмотря на то что их общая длина равна 1024 байтам. Смещения для длинных и коротких массивов определяются одинаковым образом. Если в приведенных выше командах, с по- мощью которых к адресу массива Т прибавлялось значение индекса J, использовать смещение, равное —1, то значение пе- ременной J будет прибавляться к величине, которая на единицу меньше значения адреса массива Т. Эти действия могут быть выполнены за счет замены в указанных командах обозначений #Т и /Т соответственно на =Н=Т—И и /Т—П. Заметим, что обо- значения =Ц= и / относятся ко всему выражению, т. е. запись /Т — II, например, эквивалентна записи /(Т—!1), а не (/Т) — !1.
Длинные массивы 283 (Однако на самом деле для транслятора LISA в подобных вы- ражениях скобки никогда не используются.) Таким же образом мы можем использовать для длинных массивов любые выражения для смещений, с которыми мы встречались в статьях 23 и 32. В частности, можно использовать массивы Т, которые начинаются с элемента Т(1) или некоторого другого Т(&), где £=/=0; выражения вида Т(е+&) или Т(е—k), где k — константа (возможно, 16-разрядная); смещения в по- следовательных длинных массивах. Упражнения 1. Напишите предложения языка Бейсик, соответствующие каждой из приведенных ниже последовательностей команд язы- ка ассемблера. При этом Т представляет собой длинный массив 8-разрядных величин, первым элементом массива является Т(0) ; ячейки ZP и ZP-|-!1 находятся на нулевой странице, ячей- ка ZP очищена; переменные J и К являются 16-разрядными. (a) LDA J CLC ADC К TAY LDA J+11 ADC К+П РНА TYA CLC ADC #Т TAY PLA ADC /Т STA ZP+ll LDA (ZP), Y STA W * (6) LDA #T CLC ADC J TAY LDA /Т ADC J+!l STA ZP+!1 LDA (ZP), Y
284 Статья 77 INY BNE ALPHA INC ZP+!1 ALPHA STA (ZP), Y (в) LDA #T CLC ADC К TAY LDA /Т ADC K+!l STA ZP+ll LDA (ZP), Y CLC ADC #T TAY LDA /Т ADC #!0 STA ZP+!1 LDA #!0 STA (ZP), Y 2. Сколько байтов и машинных циклов можно сэкономить в первой программе настоящей статьи за счет указанных усо- вершенствований? Не забудьте, что ячейки ZP и ZP-}-!! нахо- дятся в нулевой странице. 3. Напишите фрагмент программы в виде цикла, где для всех N элементов длинного массива Т элемент T(J) устанавливается равным 0, если J четное, и равным 1, если J нечетное. Сначала установите значение элемента Т(0), затем Т(1) и т. д. до T(N), где N — 2-байтовая величина (заметим, что при этом обраба- тывается N-H различных элементов). Используйте постиндекс- ную косвенную адресацию с ячейками ZP и ZP-f-ll на нулевой странице; пусть содержимое ячейки ZP постоянно равно нулю. Для управления циклом перешлите 2-байтовую величину N в 2-байтовую ячейку N1 и с помощью приведенной ниже после- довательности команд уменьшайте на 1 значение N1 до тех пор, пока оно не станет равным 0: DEC N1 BNE ALPHA DEC Nl-f-П BNE ALPHA
Модификация адресов 285 Заметим, что при использовании приведенной последовательно- сти команд возникает трудность из-за того, что выход из цикла произойдет здесь после декремента значения 0101 (шестнадца- теричное), а не 0001, как это должно было бы быть. Для полу- чения правильного числа шагов в цикле можно увеличить на 1 содержимое ячейки N1+11 в процессе инициализации, если только N1 не равно 0 (если 2-байтовая величина N1 не исполь- зуется ни в каких других целях, кроме как для счета числа шагов). Статья 78 Модификация адресов Теперь мы опишем замечательный прием, позволяющий во многих случаях сэкономить память и ускорять выполнение про- грамм, предназначенных для процессора 6502. Прием этот имеет несколько модификаций и массу вариантов использования, хотя и не свободен от двух недостатков. Мы начнем его изучение с примера улучшенного метода обработки длинных массивов, где, в частности, используется и этот прием. Рассмотрим подпрограмму, приведенную ниже .в двух фор- мах— на языке ассемблера и на машинном языке (метка LELA обозначает Load element of long array — загрузка элемента длинного массива): 0840 AD FE 10 LELA LDA ARRAY 0843 ЕЕ 41 08 INC LELA+11 0846 D0 03 BNE LELA1 0848 ЕЕ 42 08 INC LELA+12 084В 60 LELA1 RTS Что происходит после вызова подпрограммы LELA? Сначала мы загружаем первый элемент массива ARRAY, а затем выполняем инкремент ячейки LELA-{-!l. Что это за ячейка? Это один из- байтов команды — именно, байт, содержащий FE. После опера-
286 Статья 78 ции инкремента в этой ячейке будет FF. Поскольку это не нуль, мы переходим на метку LELA1 и выходим из подпрограммы. Допустим, мы снова вызываем LELA. Ячейки 0840, 0841 и 0842 теперь содержат AD, FF (не FE) и 10. Что произойдет? Вспомните, что выполняется не программа на языке ассемблера, а машинная программа. Команда AD FF 10 загружает регистр А вторым элементом массива ARRAY, расположенным по адресу 10FF. Затем мы снова .выполняем инкремент ячейки LELA-f-!l, изменяя ее содержимое с FF на 00, и поскольку теперь в ней нуль, мы выполняем также инкремент ячейки LELA+!2, изменяя ее содержимое с 10 на 11. Теперь в ячейках 0840, 0841 и 0842 содержатся коды AD, 00 и 11 (последовательность команд INC-BNE-INC осуществляет инкремент 16-разрядного слова, как это было показано в статье 30). При третьем вызове подпрограммы LELA мы выполняем команду AD 00 И, которая загружает третий элемент массива ARRAY и т. д. Каждый вызов LELA приводит к загрузке эле- мента массива и к изменению программы так, чтобы в следую- щий раз был загружен следующий элемент. При этом благодаря использованию инкремента 16-разрядного слова описанная ме- тодика годится и для коротких, и для длинных массивов. Это и называется модификацией адреса. Мы модифицируем, т. е. изменяем адрес в команде; в рассмотренном примере мы прибавили к адресу единицу. Можно также для модификации адреса воспользоваться операцией записи; так, приведенные ниже строки устанавливают нашему адресу начальное значение .(FE и 10): LDA #ARRAY ; Правые 8 разрядов STA LELA+I1 ; адреса массива LDA /ARRAY ; Левые 8 разрядов STA LELA+I2 ; адреса массива Если подпрограмма LELA вызывалась несколько раз, эта после- довательность команд заставляет ее начать загрузку заново, с начала массива. Теперь о недостатках. Во-первых, в начале нашей программы надо всегда выполнять приведенные выше строки в качестве элемента процесса инициализации. Об этом легко забыть, или понадеяться, что такая инициализация не нужна, так как мас- сив обрабатывается только один раз; если, однако, наша про- грамма впоследствии превратится в подпрограмму и будет вы- зываться неоднократно, то и массив будет обрабатываться не- однократно, и каждый раз обработка должна начинаться с на- чала массива. Второй недостаток серьезнее. Он связан с использованием в
Модификация адресов 287 микроЭВМ. двух видов запоминающих устройств. Один вид — обычная память, которая по историческим причинам обычно- называется запоминающим устройством с произвольной выбор- кой (ЗУПВ); другой вид — постоянное запоминающее устройства (ПЗУ). Мы уже упоминали в статье 70, что разработчики вы- числительной аппаратуры используют термины «считать из па- мяти» и «записать в память» для описания процессов, которые мы называли загрузкой и записью. Так вот, ПЗУ — это память,, допускающая только загрузку, но не запись. ПЗУ дороже, чем ЗУПВ, но они часто используются в тех случаях, когда надо защитить программу (например, монитор) от разрушения при возникновении ошибки адресации в програм- ме пользователя. Вы можете записать в ПЗУ любую информа- цию, которую никогда не будете изменять; сюда входят все ваши константы, а также почти все команды программы. Одна- ко команду, помеченную меткой LELA, нельзя хранить в ПЗУ, потому что в ходе выполнения программы вы записываете в эту ячейку новые числа (и выполняете ее инкремент, что также предполагает запись в ячейку нового содержимого). Это спра- ведливо для любой команды с модифицируемым адресом. В ЭВМ Эпл программы пользователя хранятся в ЗУПВ (хотя коды команд Бейсика и монитор — в ПЗУ). Поэтому в про- граммах можно использовать модификацию адреса. Даже в вычислительных системах, ориентированных главным образом на ПЗУ, всегда есть и ЗУПВ, хотя бы небольшого объема; прак- тически в любой вычислительной системе имеющегося объема ЗУПВ хватит для хранения нескольких подпрограмм (подобных LELA), использующих модификацию адреса. При этом в ЗУПВ должны находиться только сами подпрограммы; строки инициа- лизации и вызова подпрограмм могут быть записаны в ПЗУ. Начальный адрес, используемый в нашей модифицируемой команде, является фиктивным: он «перекрывается» в процессе инициализации, поэтому нет необходимости задавать его как ARRAY. Вместо LDA ARRAY можно написать LDA MODIFY (дав предварительно определение MODIFY EQU $FFFF или с любым другим 16-разрядным адресом) для напоминания о том, что здесь используется модифицируемый адрес. Далее, ко- манду LDA можно заменить на STA; в результате получится подпрограмма записи в «следующий байт» длинного массива- содержимого регистра А. Упражнения 1. Напишите последовательность команд на языке ассембле- ра с использованием модификации адреса для выполнения опе- рации W=T(J), где Т — длинный массив, a J — 16-разрядна»
288 Статья 79 величина. Используйте метод, описанный в предыдущей статье, заменив адресацию к нулевой странице модификацией адреса (с фиктивным адресом MODIFY). 2. Напишите последовательность команд, изменяющую ко- манду «загрузить T(J)» на команду «загрузить T(J—1)». Ко- манда расположена по метке L. (Подсказка: вспомните мате- риал статьи 30.) 3. Программу LELA из этой статьи можно написать по- другому, заменив первые две команды на следующие: LELA LDA ARRAY, Y INY Регистр Y следует инициализировать нулем, поэтому вместо команд LDA #ARRAY STA LELA-f-Il которые инициализируют ячейку LELA+11, надо написать LDA (пРи этом ячейка LELA-H1 не модифицируется). Опишите преимущества и недостатки этого варианта программы. Статья 79 Параметры подпрограмм Модификация адреса полезна не только при обработке длин- ных массивов, но и во многих других случаях. Важнейший из них — передача параметров в подпрограмму. Ранее (см. описа- ние подпрограмм MULT и DIV) мы видели, что, используя ре- гистры, можно передать в подпрограмму и получить из подпро- граммы до трех байтов информации. Если, однако, число пара- метров больше трех, или параметром является имя массива, следует применять другие способы. Предположим сначала, что подпрограмма использует один параметр — имя массива. Пусть адрес массива передается в программу через регистры А и X: старшее полуслово через ре-
Параметры подпрограмм 289 гистр А, младшее — через регистр X. Тогда вызов подпрограммы G(U) осуществляется так: LDA /U LDX #U JSR G Пусть теперь G определяется в общем случае как G(T), где Т в приведенном примере имело значение U, но при следующем обращении к подпрограмме G может быть чем-то другим (мы в этом случае называем Т формальным параметром, a U — со- ответствующим ему фактическим параметром). Предположим далее, что ALPHA — метка какой-либо команды с обращением к массиву Т, например: ALPHA LDA MODIFY, X Тогда в начале подпрограммы G мы должны выполнить ко- манды STX ALPHA-}-! 1 STA ALPHA4-I2 Заметьте, что сначала записывается содержимое регистра X, т. е. младшее полуслово адреса, а затем — содержимое регист- ра А, т. е. старшее полуслово (поскольку байты адреса пере- ставлены) . Если в подпрограмме G имеется несколько команд с обраще- нием к массиву Т, мы должны сначала записать в адресную часть этих команд содержимое регистров А и X. Набор пар ко- манд STA—STX в начале программы называется преамбулой, или заголовком. В общем случае заголовок — это часть строк инициализации подпрограммы, в которых происходит запись фактических значений адресов — параметров в требуемые ячейки. Теперь предположим, что подпрограмма G имет два пара- метра, Т1 и Т2, причем оба параметра — имена массивов. На- пример, G может быть программой, в которой массив Т1 пере- сылается по адресу Т2. В этом случае у нас есть два адреса, по 2 байта каждый, которые надо передать из вызывающей про- граммы в подпрограмму; для этого недостаточно трех регистров А, X и Y. Одно из решений этой проблемы заключается в про- талкивании адресов Т1 и Т2 в стек перед вызовом G: LDA /Т1 ; Адрес Т1 в стек РНА ; (старшее полуслово) LDA #Т1 ; Адрес Т1 в стек РНА ; (младшее полуслово) 19—589
290 Статья 79 LDA /Т2 ; Адрес Т2 в стек РНА ; (старшее полуслово) LDA #Т2 ; Адрес Т2 в стек РНА ; (младшее полуслово) JSR G ; Адрес возврата в стек Подпрограмма G должна вытолкнуть эти величины из стека. Сначала она выталкивает адрес возврата и сохраняет его в ре- гистрах X и Y; затем она выталкивает 4 адресных байта и мо- дифицирует адреса (учитывая при этом, что эти байты вытал- киваются в порядке, противоположном проталкиванию); нако- нец, она проталкивает адрес возврата назад в стек, чтобы обес- печить правильное выполнение команды RTS. Заметьте, что все, что проталкивается в стек, затем выталкивается из него, хотя G PLA ; Младший байт ТАХ ; адреса возврата в регистр X PLA ; Старший байт ТАУ ; адреса возврата в регистр Y PLA ; Младший байт STA ВЕТА+Н адреса Т2 в ВЕТА PLA ; Старший байт STA ВЕТА+Г2 ; адреса Т2 в ВЕТА PLA ; Младший байт STA ALPHA+H ; адреса Т1 в ALPHA PLA ; Старший байт STA ALPHA+12 ; адреса Т1 в ALPHA ТУА ; Поместить адрес возврата РНА ; назад в стек, ТХА ; выталкивая в порядке, РНА ; обратном проталкиванию LDX #!0 ; Начальное значение индекса ALPHA LDA MODIFY.X ; Переслать 1 байт из ВЕТА STA MODIFY.X ; 1-го массива во 2-й INX ; К следующему ; пересылаемому байту СРХ N ; Это был последний байт? BNE ALPHA ; Если нет, повторить RTS ; Конец подпрограммы Рис. 79.1. Подпрограмма с заголовком.
Параметры подпрограмм 291 4 адресных байта проталкиваются в основной программе, а вы- талкиваются в подпрограмме. Подпрограмма G пересылки N байтов из Т1 и Т2 может вы- глядеть, как показано на рис. 79.1. Конечно, N также может быть параметром, и в этом случае заголовок будет длиннее. (Очевидно, что и для параметров-переменных, таких, как N, и для параметров-массивов, таких, как Т1 и Т2, используются од- ни и те же приемы модификации адреса.) Упражнения 1. Объясните, почему на рис. 79.1 команда STA ВЕТА-Н1 стоит перед командой STA BETA+I2. 2. Напишите подпрограмму ARRAYO (Т, N), которая прирав- нивает нулю элементы массива от Т(1) до T(N). При входе в подпрограмму значение N находится в регистре Y, а адрес Т в регистрах А и X (старшее полуслово в А, младшее— в X). Тогда, например, для того чтобы приравнять нулю элементы массива от U (1) до U (16), надо будет написать: LDA /U LDX #U LDY #!16 JSR ARRAYO Используйте модификацию адреса; декремент индекса выпол- няйте в регистре Y. 3. Пусть при входе в подпрограмму на рис. 79.1 адрес Т1 находится в стеке, а адрес Т2 — в регистрах А и X. Внесите в подпрограмму необходимые изменения. (Это гибрид двух мето- дов, изложенных в данной статье.) Подпрограмма G будет вы- зываться следующим образом: LDA /Т1 РНА LDA #Т1 РНА LDA /Т2 LDX #Т2 JSR G 19*
292 Статья 80 Статья 80 Модификация данных в командах с непосредственной адресацией Мы описали модификацию адресов в 3-байтовых командах, однако модифицировать можно и константы в 2-байтовых командах. Таким образом удается сэкономить значительный объем памяти. Вообще, как будет показано далее, в програм- мах для ЭВМ. Эпл нет никакого смысла иметь выделенные об- ласти байтов с данными, кроме тех, которые входят в состав массивов. Пусть у нас есть байт данных с именем М. Очевидно, в про- грамме будет по крайней мере одна команда с обращением к М (помимо команды записи). Допустим, что это команда за- грузки (LDA М). Замените ее на команду загрузки константы, например нуля (LDA #!0), присвойте этой команде метку (на- пример, LOADM) и храните во втором байте этой команды са- му величину М. Когда вам нужно будет записать величину М, запишите ее по адресу LOADM-j-П. Это и есть модификация данных в команде с непосредственной адресацией; вы изменяете данные, входящие в состав команды, которая расположена по адресу LOADM. Модификацию данных можно выполнить с помощью команды STA LOADM-H1 но нагляднее изобразить ту же команду как STA М, если М оп- ределено в конце программы предложением М EQU LOADM+I1 Теперь все команды в вашей программе, содержащие обра- щение к М (кроме команды с меткой LOADM), должны обра- щаться ко второму байту команды с этой меткой. Это экономит 2 байта — сам байт М и еще один байт, поскольку команда с меткой LOADM из 3-байтовой превратилась в 2-байтовую. Кро- ме того, каждый раз при выполнении команды по адресу LOADM экономится 2 машинных цикла, поскольку эта команда из 4-цикловой стала 2-цикловой. Чтобы добиться максимальной оптимизации программы, вы должны выбрать в качестве моди- фицируемой (с меткой LOADM) из всех команд с непосредст- венной адресацией и с обращением к М ту, которая выполняет- ся наиболее часто.
Данные в командах с непосредственной адресацией 293 2-байтовые данные модифицируются точно так же, но 2 бай- та 16-разрядной величины Q уже не будут иметь адреса Q и Q+! 1. Для каждой такой величины Q в программе обычно име- ются две команды с непосредственной адресацией, выполняющие операции с двумя половинами величины Q. Обе они теперь из- меняются на команды с обращением к константам. На рис. 80.1 этот метод проиллюстрирован применительно к первой програм- ме сравнения 2-байтовых величин из статьи 29. Байты величины DECIDE LDA CMP BNE LDA CMP BCC P+!l Q+H DECIDE P Q LESS LDA P+!l D1 CMP #!0 BNE DECIDE LDA P D2 CMP #’0 DECIDE BCC LESS QHI EQU Dl+’.l QLO EQU D2+!l ; Сравнить старшее полуслово Р ; со старшим полусловом Q ; Если не равны, на DECIDE ; Сравнить младшее полуслово Р ; с младшим полусловом Q ; Если P<Q, переход на LESS а) ; Сравнить старшее полуслово Р ; с QHI (хранится во 2-м байте) ; Если не равны, на DECIDE ; Сравнить младшее полуслово Р ; с QLO (хранится во 2-м байте) ; Если Р< (QHI, QLO), на LESS ; Определить, где находится QHI ; Определить, где находится QLO б) Рис. 80.1. Модификация данных в командах с непосредственной адресацией. Q обозначены QHI и QLO, и именно так к ним надлежит обра- щаться в других командах. При использовании модификации данных в командах с не- посредственной адресацией сокращается время выполнения опе- рации сохранения и восстановления, если восстановление осу- ществляется командой, отличной от команды загрузки. Вместо команд STA TEMP и ADC TEMP (см. начало статьи 33) на- пишите STA TEMP и TEMPL ADC =Н=!0. а в конце программы предусмотрите определение величины TEMP: TEMP EQU TEMPL+il. Это экономит при каждом выполнении команды 2 машинных цикла и к тому же 2 байта памяти. Процессор 6502 допускает непосредственную адресацию только в командах LDA, LDX, LDY, ADC, SBC, СМР, СРХ, CPY, AND, ORA и EOR. Отсюда следует, что модификация дан- ных невозможна в тех случаях, когда некоторая величина толь- ко записывается и сдвигается или только записывается и умень*
294 Статья 80 шается на единицу командой декремента (например, в счетчике цикла). Описанная модификация данных редко оказывается эффек- тивнее хранения данных на нулевой странице, если у вас на ну- левой странице есть для этого место. Использование модифика- ции данных не приводит к оптимизации программы (ни по объ- ему памяти, ни по времени выполнения) даже для простой по- следовательности команд, подобной описанной выше STA TEMP и ADC TEMP, если TEMP хранится на нулевой странице. Един- ственным исключением является случай, когда команда ADC стоит в цикле и выполняется п раз, а команда STA стоит вне цикла; в этом случае командой ADC экономится п машинных циклов, в то время как командой STA расходуется только один лишний цикл. Псевдокоманды EQU, используемые при модификации дан- ных, обычно пишутся в конце программы. Дело в том, что если метка, например LOADM или TEMPL, входит в состав выраже- ния в псевдокоманде EQU (с правой стороны от EQU), эта метка должна быть определена перед псевдокомандой EQU (другими словами, EQU должна стоять после помеченной коман- ды или данных). Если же мы, например, напишем Р EQU Q+!l Q EQU Р—!1 то ассемблер LISA, пытаясь определить значения Р и Q, войдет в бесконечную петлю. Выражение вроде Q-J-11, которое появля- ется в предложении с EQU перед тем, как Q получило опреде- ленное значение, называется ссылкой вперед. Такие ссылки не разрешаются системой LISA. Упражнения 1. Рассмотрите приведенную ниже программу, которая запи- сывает в ячейку NEQUAL количество элементов последователь- ного массива Т 2-байтовых величин, равных 2-байтовой величи- не, хранящейся в регистрах А и X: STA J+I1 STX J LDY #!0 LDX #!200 ALPHA LDA Т—!2, X СМР J BNE ВЕТА LDA Т—II, X
Данные в командах с непосредственной адресацией 295 СМР J+I1 BNE ВЕТА INY BETA DEX DEX BNE ALPHA STY NEQUAL >(< а) Опишите изменения, которые необходимо внести в программу, если для двух байтов величины J используется мо- дификация данных в командах с непосредственной модификаци- ей. Назовите эти байты JUPPER и JLOWER и определите каж- дый из них с помощью псевдокоманды EQU (см. подсказку к упр. 1а из статьи 74). >|< б) Если выполнить эти изменения, сколько сэкономится машинных циклов (минимум и максимум) и сколько байтов? в) Сколько сэкономится машинных циклов (минимум и максимум) и сколько байтов, если оба байта величины J хра- нить на нулевой странице? 2. а) Опишите изменения, которые необходимо внести в подпрограмму DIV (статья 40), если для величин DDATA1 и DDATA2 использовать модификацию данных. б) Если выполнить эти изменения, сколько сэкономится байтов и сколько машинных циклов (только минимум)? А если хранить DDATA1 и DDATA2 на нулевой странице, как в упр. 1а из статьи 74? 3. В программах входного и выходного преобразования (статьи 62 и 63) среди многочисленных байтов данных имеет- ся один (не входящий в таблицы с метками DECO5 и DECO6), который не допускает использования модификации данных. Что это за байт, и почему его нельзя модифицировать? Задача 4 для решения на ЭВМ Многоразрядное умножение Напишите программу, которая принимает в качестве вход- ных данных два целых числа без знака, имеющих произвольную длину и вводимых с клавиатуры (каждое отдельной строкой), и выводит их произведение. В вашей программе должно быть два массива (назовите их Ml и М2) для разрядов обоих перемножаемых чисел и третий массив (назовите его М3) для произведения. В программе долж- на быть предусмотрена операция записи десятичных разрядов каждого из чисел (самих десятичных разрядов, а не их сим- вольных кодов) в байты соответствующих массивов. Для пере-
296 Статья 81 множения чисел следует воспользоваться методом, сходным с тем, который все мы знаем еще из средней школы. При перемно- жении двух цифр вы получаете двухразрядное число; вы пере- носите первую цифру и записываете вторую. (Не используйте подпрограмму MULT. Либо выполняйте повторное сложение — 3x8=84-8+8, либо создайте таблицы и предусмотрите их про- смотр.) Как и раньше, ввод сомножителей выполните с помощью подпрограммы GETLNZ. Не забудьте, что GETLNZ использует регистры А, X и Y. Выведите на экран результат умножения и вернитесь в начало программы на ввод следующей пары чисел. Статья 81 Модификация относительных адресов Модифицировать можно не только данные в командах с не- посредственной адресацией, но и относительные адреса. Один из примеров использования этого приема — реализация анало- га вычисляемого оператора GO ТО Фортрана или предложения ON ... GOTO Бейсика. Например, в обоих предложениях ON К GOTO 11, 12, 13, 14, 15 (Бейсик) GO ТО (11, 12, 13, 14, 15 ), К (Фортран) осуществляется переход на метку 11, если К=1, на метку 12, если К=2, на метку 13, если К=3, на метку 14, если К=4 и на метку 15, если К=5. Предположим теперь, что К находится в регистре X, и что метки 11, 12, 13, 14 и 15 представлены в программе на ассемб- лере метками Lil, L12, L13, L14 и L15 соответственно. Тогда мы можем написать DEX ; Если К=1, BEQ L11 ; переход на L11 DEX ; Если К=2, BEQ L12 ; переход на L12
Модификация относительных адресов 297 DEX ; Если К=3, BEQ L13 ; переход на L13 DEX ; Если К=4, переход на BEQ L14 ; L14, в противном случае BNE L15 ; переход на L15 Программа займет 14 байтов1) и в зависимости от значения К 5, 9, 13, 17 или 19 машинных циклов, а в среднем —12,6 цикла (конечно, здесь предполагается, что К действительно имеет зна- чение от 1 до 5). Посмотрим теперь, как можно улучшить эту программу, ис- пользуя модифицированный относительный адрес в команде с меткой MODREL, которая имеет вид MODREL BNE * Эту команду можно модифицировать так, что она осуществит переход на любую из меток Lil, L12^L13, L14 или L15; необхо- димые для реализации переходов относительные адреса хранят- ся в таблице. Пусть непосредственно за модифицируемой коман- дой следует команда с меткой MODR1; тогда MODRl+r=Z>, где г — относительный адрес, соответствующий переходу по ад- ресу Ь, отсюда r=b—MODR1. Таблицу относительных адресов можно определить так: RELADS Lil — MODR1 L12 —MODR1 L13 —MODR1 L14 —MODR1 L15 —MODR1 BYT BYT BYT BYT BYT Наш вычислительный положениях может быть оператор GO ТО при сделанных пред- выражен следующим образом: MODREL MODR1 LDA. RELADS-11, X STA MODREL 4-!l BNE * (следующая команда) ; Загрузить относит, адрес ; из таблицы и записать его ; Переход по модиф. адресу Заметьте, что мы не можем использовать команду JMP с моди- фицируемым адресом, потому что JMP требует обычного 16-раз- рядного адреса. Мы вынуждены использовать условный пере- ход; но что произойдет, если условие перехода не будет выпол- Обратите внимание на использование команды BNE (которая здесь безусловно выполняет переход), а не JMP (см. статью 27). Если бы мы ис- пользовали JMP, программа заняла бы 15 байтов.
298 Статья 81 нено? Команда BNE отлично решает эту проблему. Допустим, например, что К равно 3. Мы загружаем содержимое ячейки RELADS+2 (т. е. RELADS —14-3) в регистр А. Теперь в ре- гистре А значение L13— MODR1, и оно, по-видимому, не равно О, так что после записи этого значения (назовем его г) во вто- рой байт команды BNE с меткой MODREL, эта команда дей- 4 ствительно выполняет переход; по правилам относительной ад- * ресации переход происходит по адресу MODR14-r=MODR14- 4-(L13 — MODR1) =L13, т. e. как раз туда, куда надо. ; (Описанный прием замечателен тем, что программа выпол- f няется правильно даже и в том случае, когда команда BNE не ( выполняет переход. Это может произойти, только если относи- 5 Тельный адрес равен 0; но по правилам относительной адреса- ’ ции такая команда BNE, если бы она и выполняла переход, все равно передала бы управление наследующую строчку. Это впол- - Не обычная ситуация, поскольку метка L11, например, могла бы следовать непосредственно за меткой MODREL. В этом слу- чае метка MODR1 должна стоять на строке сама по себе без всяких команд. В ассемблере LISA это достигается использова- нием двоеточия после метки; в данном случае следовало напи- сать MODR1:.) Описанная реализация вычисляемого перехода требует 13 - байт: 8 — для команд, и 5 — для таблицы. Она выполняется за ; 11 машинных циклов (или за 10, если команда BNE не выпол- няет переход, как описано выше). Таким образом, она оптималь- нее первого варианта и по времени, и по объему памяти. В об- f щем случае модификация относительного адреса позволит сэко- \ номить память и время, если в вычисляемом операторе GO ТО имеется не менее 5 адресов перехода. ' Конечно, следует удостовериться в том, что вычисляемые от- ' носительные адреса не выходят из допустимого диапазона зна- чений (от —128 до 127). Если это не так, можно командой BNE осуществить переход на команду JMP, которая использует про- извольный 16-разрядный адрес перехода. Следует также заме- тить, что вычисляемый оператор GO ТО можно реализовать с - использованием косвенных адресов, получаемых из таблицы переходов; это, однако, потребует на каждый адрес не один байт, а два. Еще одна возможность заключается в проталкива- нии этого 2-байтового адреса и использовании затем команды RTS, которая выталкивает из стека адрес и переходит на него. Упражнения 1. Выше было отмечено, что программа с модификацией от- носительного адреса требует 10 или И машинных циклов, в то ; время как первый вариант программы в среднем требует 12,6 .
Массивы строк 299 цикла. Можете ли вы описать обстоятельства, при которых это не является улучшением? Насколько часто это обстоятельство реализуется на практике? 2. Предположите, что все переходы в рассмотренной в этой статье программе осуществляются вперед. Если это так, нельзя ли использовать вместо BNE какую-либо другую команду . условного перехода? Если можно, то улучшит ли это програм- му? Почему улучшит или почему не улучшит? 3. Рассмотрите приведенную ниже программу, которая ис- пользует модификацию относительного адреса: STX BRANCH-}-! 1 LDA Q BRANCH BNE * LSR LSR LSR LSR LSR LSR LSR STA Q >[< а) Объясните своими словами, что делает эта програм- ма. Считайте, что регистр X содержит целое число от 0 до 7. б) Что происходит, если команда BNE не выполняет переход? Будет ли в этом случае программа выполняться «пра- вильно» в каком-то смысле? >|с в) Сколько времени требует этот алгоритм для каждого значения X от 0 до 7, если команда BNE действительно выпол- няет переход? Статья 82 Массивы строк Очень часто программа использует большое количество сим- вольных строк, которые хранятся в виде массива. Эти строки Могут представлять имена и адреса людей или, возможно, име-
300 Статья 82 на и адреса (в техническом смысле) переменных в программе. Если все строки в массиве одинаковой длины, мы можем ис- пользовать уже рассмотренные приемы. Например, если все строки имеют длину 3 символа, мы получаем массив 3-байтовых величин аналогично массивам 2-байтовых величин, которые рас- сматривались в статье 32. Чаще, однако, дело будет обстоять не так. Имена обычно имеют различную длину: это в равной Рис. 82.1. степени относится и к именам людей, и к именам программных переменных. В общем случае массив строк — это длинный массив. Даже если в программе меньше 256 строк, и даже если ни одна стро- ка не имеет длину, превышающую 256 символов, крайне мало вероятно, что на практике общее количество символов во всех строках окажется меньше 256. Предположим, что мы храним все наши строки в массиве с именем SPACE в последовательном порядке, и каждой строке предшествует запись о ее длине, как показано на рис. 82.1. Предположим далее, что у нас есть другой массив, с именем Т, в каждом элементе которого T(J) хранится некоторая инфор- мация о строке J (для простоты допустим, что Т — это обычный короткий массив байтов). Рассмотрим теперь процесс поиска, цель которого — найти в нашем массиве строку, совпадающую с другой строкой, с име- нем NAME, которая имеет ту же организацию, т. е. самой стро- ке предшествует запись о длине. В конце процесса поиска, если строка NAME совпадает со строкой J, мы загружаем J в ре- гистр X. Это позволит нам с легкостью обратиться, например, к дополнительной информации из T(J). Мы используем постиндексную косвенную адресацию; адрес начала текущей строки мы будем хранить в двух ячейках на нулевой странице ZP и ZP-j-П- Команды LDA (ZP),Y и СМР NAME,Y позволят нам сравнить один символ текущей строки с одним символом строки по имени NAME. При этом содержимое регистра Y сначала равно длине строки NAME, а затем умень- шается до 0. (Если длина текущей строки отличается от длины
LDX #$FF Установить индекс строки = —1 LDA /SPACE Установить адрес строки 0 STA ZP + !1 (первый адрес массива SPACE) LDA #SPACE в ячейках ZP и ZP + I1 STA ZP на нулевой странице LOOP INX Увеличить индекс строки CPX NSTR Если он равен полному числу BEQ NOTF строк, перейти на NOTF (не найдена) LDY #!0 Получить длину этой строки (для этой LDA (ZP).Y команды требуется Y=0) CMP NAME Если не равно длине NAME, BNE PROCI строки не могут совпадать TAY Инициализировать Y длиной строки ALPHA LDA (ZP),Y Сравнить символ строки X CMP NAME,Y с символом строки NAME BNE PROC Если не равны, строки не DEY равны Если равны, к следующему символу BNE ALPHA Есть еще символы? BEQ FOUND Если нет, строка найдена PROC LDY #!0 Перезагрузить длину строки X LDA (ZP).Y (как и раньше требуется Y=0) PROCI CLC Прибавить 1 к длине строки X ADC (чтобы пропустить байт длины) ADC ZP Прибавить к адресу в ZP и STA ZP ZP + U, чтобы там был адрес BCC LOOP ; следующей строки INC ZP + !1 ; в массиве. После этого BCS LOOP ; вернуться на LOOP Рис. 82.2. Программа поиска в массиве строк.
302 Статья 82 строки NAME, то строки не могут совпадать, и их даже не на- до сравнивать.) Если строки не совпадают, мы прибавляем к 2-байтовой ве- личине в ячейках ZP и ZP+I1 длину текущей строки плюс 1 (из-за байта, содержащего длину строки). В результате в ука- занных ячейках будет адрес начала следующей строки. Номер J выбираемой из массива SPACE строки хранится в регистре X; закончив очередной шаг цикла, мы сравниваем содержимое регистра X с полным числом строк NSTR. Если имеет место ра- венство, значит, строка NAME не входит в наш массив, потому что номера строк лежат в диапазоне от 0 до NSTR—1. В этом случае мы можем, если нужно, дополнить наш массив строкой NAME и увеличить NSTR. В начале программы мы должны переслать адрес массива SPACE в ячейки нулевой страницы, чтобы инициализировать их для обработки строки 0. Вся программа приведена на рис. 82.2. Заметьте, что мы используем в ней новый вид цикла, где сравнение (командой СРХ) выполняется в начале каждого ша- га цикла. В результате, если NSTR равно 0, цикл не выполня- ется вовсе (в большинстве циклов один шаг всегда выполняет- ся, даже если в счетчике цикла нуль). Если данная строка най- дена, происходит переход на FOUND, в противном случае — на NOTF; после перехода на FOUND строка NAME совпадает со строкой J, причем J находится в регистре X. Пусть теперь нам надо найти J-ю строку, причем J только что было вычислено. Используя описанный выше способ, мы должны будем просмотреть все строки от 1-й до J-й, на что по- требуется значительное время. Поэтому такой способ целесооб- разно использовать только в тех случаях, когда мы просматри- ваем строки по порядку (строку 1, строку 2 и т. д.). Другой, более общий способ хранения массивов строк обсуждается в статье 85. Упражнения 1. Какие изменения надо внести в программу на рис. 82.2, если вместо ячейки NSTR, содержащей число строк, мы вводим дополнительный пустой байт (т. е. байт, в котором записан 0) в конце последней строки? (Для программы этот байт выглядит, как еще один байт длины, но только в нем всегда 0.) 2. Какие изменения надо внести в программу на рис. 82.2, если организован еще один массив с именем LB, в котором хра- нятся все байты длин строк? (В этом случае LB(J) содержит дли- ну J-й строки, причем в массиве SPACE уже нет байтов с длина- ми.) Учтите, что ZP и ZP-|-! 1, как и раньше, должны содержать адрес байта, предшествующего первому байту текущей строки,
Массивы шестнадцатеричных цифр 303 хотя теперь это уже не байт длины. (Пусть строке NAME по- прежнему предшествует ее собственный байт длины.) 3. Какие изменения надо внести в программу на рис. 82.2, если байтов длин нет, а каждая строка заканчивается пустым байтом? (Заметьте, что эти байты не эквивалентны пустому бай- ту из упр. 1; ячейка NSTR здесь по-прежнему требуется.) Вы можете считать, что место в программе, где «поиск закончен — строка NAME совпадает со строкой J» имеет метку DONE. Пусть на этот раз строке NAME не предшествует байт длины; в ячейках ZP и ZP+I1 храните адрес первого байта текущей строки (а не этот адрес минус 1) и двигайтесь по строке в про- цессе сравнения вперед, а не назад. (Начните цикл в точке ALPHA командой INY.) Статья 83 Массивы шестнадцатеричных цифр В статье 32 мы рассмотрели массив величин, каждая из ко- торых имеет длину 2 байта. Рассмотрим теперь массив вели- чин, каждая из которых имеет длину полубайта. Один полубайт содержит 4 бита, или одну шестнадцатеричную цифру (или од- ну десятичную цифру в упакованной строке, см. статью 66). Если у нас есть массив полубайтов, то хранить каждый по- лубайт в отдельном байте слишком расточительно с точки зре- ния расходуемой памяти. Если у нас есть два массива шестнад- цатеричных цифр, Т1 и Т2, мы можем хранить Т1 (J) в первых четырех двоичных разрядах элемента T(J), принадлежащего массиву байтов Т, а Т2 во вторых четырех разрядах этого эле- мента. Элементы Т1 (J) и T2(J) можно получить с помощью сдвигов и маскирования. Однако в дальнейшем мы будем по- лагать, что у нас есть только один массив шестнадцатеричных цифр, каждый байт которого хранит 2 шестнадцатеричные циф- ры. Пусть Н1 представляет собой такой массив. Байт 0 этого массива содержит элементы (Н1(0) и Hl (1); байт 1—элементы Н1(2) и Н1(3) и т. д. (рис. 83.1). Произвольный байт k массива Н1 содержит элементы Hl (2k) (в первых четырех двоичных раз- рядах) и Hl (2k+1) (во вторых четырех разрядах).
304 Статья 83 Пусть теперь регистр А содержит индекс J, и мы хотим за- грузить элемент H1(J). В статье 32 было показано, что, если у нас есть массив 2-байтовых величин, мы должны прежде всего умножить J на 2. Работая с массивом полубайтов, мы должны разделить J на 2. Кроме того, нам надо уметь как-то опреде- лять, к каким четырем разрядам найденного байта обращать- ся — к первым или ко вторым. Рис. 83.1. Последовательный массив шестнадцатеричных цифр. К счастью, это можно выполнить весьма эффективно с помо- щью команды LSR, которая не только делит содержимое реги- стра А на 2, но и сдвигает его правый бит в разряд переноса, как было показано в статье 31. Заметьте, что правый бит любо- го целого числа показывает, четное это число или нечетное. Та- ким образом, загрузку элемента H1(J) можно выполнить сле- дующим образом: LSR ; Разделить J на 2 TAY ; Загрузить байт с Hl (J) LDA Hl, Y ; и сохранить флажок переноса ВСС ALPHA ; Левые или правые 4 разряда? AND ф % 00001111 ; Правые (флажок переноса BCS ВЕТА ; устан., так что ; переход безусл.) alpha lsr ; Левые 4 разряда. Сдвинуть LSR ; их вправо, LSR ; очистив при этом LSR ; левый полубайт ВЕТА (следующая команда)
Массивы шестнадцатеричных цифр 305- Массив шестнадцатеричных цифр, как и любой другой мас- сив, обычно обрабатывается последовательно от начала до кон- ца. Приведенная ниже подпрограмма записывает шестнадцате- ричную цифру в такой массив и смещает указатель к следую- щему элементу, так что ее можно вызвать п раз, чтобы записать цифры от Н2(0) до H2(n—1) в порядке их поступления. Индекс байта массива Н2 хранится в регистре Y. В подпрограмме пре- дусмотрен специальный флажок с именем HFLAG, представляю- щий собой индикатор полубайта; если он равен 0, следующая цифра записывается в левые четыре разряда следующего байта, если же он равен —1, следующая цифра записывается в правые четыре разряда: HSTORE INC HFLAG ; Прибавить 1 к HFLAG. Если BNE HST1 ; там было— 1, теперь будет 0 ORA Н2, Y ; Заполнить левые 4 бита ; А из Н2 STA Н2, Y ; Записать в текущий байт RTS ; и закончить HST1 ASL ; Сдвинуть правые 4 бита ASL ; влево.и ASL ; записать их ASL ; в следующий байт INY ; (увеличить индекс байта, STA Н2, Y ; затем записать) LDA 4$= $FF ; Заслать—1 назад в HFLAG STA HFLAG ; для следующего вызова RTS ; и закончить Здесь ячейку HFLAG и регистр Y следует инициировать: LDY ф=!0 Начальное значение STY HFLAG ; индикатора О DEY ; В регистре Y — 1 Другой способ организации флажка HFLAG — использование только одного правого разряда этой ячейки. Флажок в этом слу- чае можно установить (или сбросить) одной командой INC HFLAG (или DEC HFLAG); проверяется флажок сдвигом его в разряд переноса (с помощью LDA HFLAG и LSR) и выпол- нением затем ВСС или BCS. Монитор Эпл имеет три подпрограммы для вывода на экран полубайтов: JSR PRHEX выводит один полубайт (правый) из 20—589
.306 Статья 83 регистра A; JSR PRBYTE выводит два полубайта из регистра А; JSR PRNTAX выводит четыре полубайта, два из регистра А и еще два из регистра X. Эти подпрограммы следует определить в программе для ассемблера LISA: PRHEX EQN $FDE3 PRBYTE EQU $FDDA PRNTAX EQU $F941 Упражнения 1. Какие изменения надо внести в программу HSTORE, чтобы работать с длинным массивом НЕХ2? Используйте мо- дификацию адреса; не забудьте, что обе команды STA (так же как и команда ORA) должны иметь модифицируемые адреса. {Подсказка: сначала определите, в какой именно точке про- граммы следует модифицировать адрес.) 2. Наиболее распространенными в английских словах яв- ляются 12 букв Е, Т, А, О, I, N, S, Н, R, D, L и U. Представьте себе, что у нас есть файл, состоящий исключительно из слов (на- пример, файл, обрабатываемый текстовым процессором). Мы можем сократить объем памяти, занимаемой этим файлом, если преобразовать его в полубайтовый код. В этом коде пробел со- ответствует полубайту 0; каждая из приведенных выше букв соответствует полубайтам от 1 до 12; а любой другой символ, ко- торый может встретиться в файле, соответствует одному из по- лубайтов 13, 14 или 15, за которым следует полубайт, опреде- ляющий конкретный символ (заметьте, что мы имеем 61 код: от О до 12; от 13—0 до 13—15; от 14—0 до 14—15 и от 15—0 до 15—15). Опишите словами алгоритм программы, преобразую- щей файл в указанный код. Считайте, что преобразуемый файл целиком находится в памяти, образуя массив С, начинающийся с С(1); новый файл также целиком находится в памяти, при этом он образуется с помощью подпрограммы HSTORE, опи- санной выше. Считайте также, что исходный файл заканчивает- ся пустым байтом, а новый файл должен закончиться кодом 15—15. Приведите все строки вызова подпрограммы и инициа- лизации этой подпрограммы. >|с 3. Напишите подпрограмму, использующую PRBYT и эки- валентную подпрограмме PRNTAX. Постарайтесь использовать минимум команд. {Подсказка: загляните в конец статьи 61. За- метьте, что PRBYT не использует регистр X.)
Сортировка 307 Статья 84 Сортировка Слово «сортировка» в вычислительной технике имеет специ- фическое значение. Сортировать — это значит располагать дан- ные в определенном порядке; обычно в порядке возрастания, на- чиная с наименьших значений и кончая небольшими. Предположим, что у нас есть набор чисел: 212, 312, 213, 202, 415, 617, 305. Эти числа хранятся в массиве Т, причем Т(1) = =212, а Т(7)=305. Мы хотим расположить их в порядке воз- растания: 202, 212, 213, 305, 312, 415, 617. В таком порядке данные можно переслать в другой массив U, при этом U(l) = =202, a U (7) =617. Или нам может потребоваться сортировка на месте, когда данные возвращаются в массив Т, но теперь Т(1) =202 и Т(7)=617. Простейший способ выполнить сортировку на месте заклю- чается в следующем. Просмотрите массив Т, сравнивая пары соседних чисел: Т(1) с Т(2), затем Т(2) с Т(3) и т. д. до пары Т(6) и Т(7) (в данном примере). Если числа в паре расположе- ны по порядку, т. е. T(J)i^T(J-j-l), оставьте их без изменения. В противном случае поменяйте их местами, т. е. выполните на- значение T(J)=T(J+1) и наоборот. После завершения первого прохода массива (начинающего- ся с пары Т(1) и Т(2) и завершающегося парой T(N —1) и T(N), где для нашего примера N=7) вернитесь и выполните второй проход. Продолжайте выполнять проходы до тех пор, по- ка во всем проходе не будет сделано ни одной перестановки чи- сел; это-значит, что все пары T(J) и T(J+1) расположены по порядку. На этом надо прекратить сортировку. На Бейсике рассмотренный алгоритм выглядит следующим образом: 10 J=1 20 К=0 30 IF T(J)<=T(J+1) THEN 80 40 Z=T(J) 50 T(J) = T(J+1) 60 T(J+1)=Z 70 K=1 80 J=J-|-1 90 IF JON THEN 30 100 IF KOO THEN 10 20*
308 Статья 84 LDA N ; Вычислить 2>|<N (последовательный ASL ; массив из N 2-байтовых величин ц STA TWON ; имеет длину 2>fccN байт) ALPHA LDX 4M0 ; Начальное значение флажка=0 j STX К ; Начальное значение индекса =2 । LDX #!2 ; (т. е. 2>|<J, причем J=l) ВЕТА LDA Г+!1,Х ; Сравнить T(J+1) с Т (J) CMP T— !1,X ; (2>fccJ в регистре X), BNE DECIDE ; отсчитывая элементы Т LDA T,X ; от Т(1) до T(N). Если Т (J+1) CMP T-!2,X ; больше или равно (Т (J) меньше DECIDE BCS GAMMA ; или равно), пропустить обмен LDA r,x ; Это надо повторить, если ранее LDY T—!2,X ; произошел переход по BNE STA T—!2,X ; Здесь происходит обмен, сначала TYA ; младшие полуслова (заметьте, что STA T,X ; переменная Z нам не нужна) LDA T—!1,X ; Теперь старшие. Заметьте, что LDY T+!1,X ; есть индексированная загрузка Y, STA T+!1,X ; но нет индексированной записи TYA ; из Y, кроме как на нулевую ; страницу. Поэтому используем STA T—!1,X ; TYA и индексированную запись ; из А :INC К ; Установить флажок (ненулевое ; значение) ! GAMMA INX ; Прибавить 2 к индексу * INX ; (для обращения ; к следующей 2-байтовой величине) CPX TWON ; Если 2>kJO2>KN (т. е. J<>N), BNE BETA ; перейти к следующей паре LDA К ; Если КОО (обмены были) BNE ALPHA ; выполнить следующий проход Рис. 84.1. Сортировка массива 2-байтовых величин. Переменная К представляет собой флажок. Каждый раз, когда выполняется перестановка (строки от 40 до 60), мы уста- навливаем К=1 (строка 70). В начале же каждого прохода устанавливаем К=0 (строка 20). Если весь проход выполняет- ся без единой перестановки, то К останется равным 0 (строка 100). Переменная Z нужна потому, что мы не можем просто вы- полнить предложения T(J)=T(J+1) и T(J+1)=T(J), чтобы поменять местами элементы массива. Если бы мы так сделали,
Сортировка 309 элементы T(J) и T(J-f-l) получили бы одно и то же значение, в то время как мы хотим, чтобы они обменялись значениями. Проверка на строке 90 отличается тем, что она не доводит J до индекса последнего элемента. J принимает значения 1,2,3 и т. д. до N —1, но никогда не становится равным N. В самом деле, когда J=N—1, мы сравниваем T(J) и T(J-J-l), т; е. T(N—1) и T(N). Это последнее сравнение при каждом проходе. Та же сортирующая программа, обрабатывающая последо- вательный массив Т 2-байтовых величин (для хранения боль- шинства использованных в примере чисел требуется 2 байта), приведена на рис. 84.1. Заметьте, что флажок К теперь прини- мает либо нулевое значение (что характеризует отсутствие пе- рестановок), либо ненулевое значение. Последнее получается добавлением 1. Переменная J назначена регистру X (собствен- но говоря, в регистре X хранится величина 2J, изменяющаяся от 2 до 2N). Переменная Z, использованная в программе на Бейсике, не нужна в программе на языке ассемблера, поскольку обмен значений двух величин можно осуществить с помощью двух регистров (как это было отмечено в статье 8). Вниматель- но следите за смещениями; поскольку массив Т начинается с Т (1), обращение к элементу T(J) выполняется по адресу Т —!2,Х (младшее полуслово) и Т —!1,Х (старшее полуслово), если в регистре X содержится 2J. Обращение к элементу T(J+1) в этом случае выполняется по адресу Т,Х (младшее полуслово) и Т+!1,Х (старшее полуслово). Флажок К, обозначающий «перестановок не было» (если он не равен 0), называется логическим флажком. Про него гово- рят, что он имеет значение истина, если он не равен 0, и значе- ние ложь, если он равен 0. Ассемблер LISA допускает исполь- зование команды BFL (branch on false — переход, если ложь) вместо BEQ и команды BTR (branch on true — переход, если истина) вместо BNE. В частности, в последней строке програм- мы на рис. 84.1 можно было вместо BNE ALPHA написать BTR ALPHA. Упражнения 1. Алгоритм работы с флажком К, использованный в про- грамме на рис. 84.1, может вызвать возражение. При значении К, равном 255, следующее прибавление 1 приведет к тому, что значение К станет равным 0. Однако в рассматриваемой про- грамме это никогда не может случиться. Почему? 2. Почему в программе на Бейсике, приведенной в этой статье, выполняется проверка условия T(J)^T(J-f-l), а не T(J) <T(J-f-l)? (Подсказка: рассмотрите очень простой случай, когда N=2 и Т(1)=Т(2), и проведите ручную прогонку.)
310 Статья 85 3. Какие изменения надо внести в программу на рис. 84.1, если использовать модификацию данных в командах с непосред- ственной адресацией? Сколько байтов можно сэкономить таким образом? (Подсказка', очевидно, что переменные в массиве Т не могут рассматриваться как модифицируемые данные. Какие же данные в этой программе можно модифицировать?) Статья 85 Упорядочение по алфавиту Упорядочение по алфавиту списка можно рассматривать как частный случай сортировки. Чтобы пояснить эту мысль, будем считать 2-байтовые вели- чины, сортируемые в программе на рис. 84.1, 2-символьными величинами. Заметьте, что мы сначала сравниваем левые бай- ты. Если они равны, мы сравниваем правые байты. Это, однако, в точности соответствует порядку сравнения 2-символьных величин, если их надо расположить по алфавиту. Рассмотрим, например, величины ME, ОН и MY. При сравне- нии ME и ОН мы фактически должны сравнить только М и О; М стоит по алфавиту перед О, поэтому ME должно стоять перед ОН. То же происходит при сравнении MY и ОН (хотя Н стоит перед Y). При сравнении же ME и MY первые буквы совпада- ют, поэтому надо сравнить Е и Y. Поскольку Е стоит перед Y, ME должно стоять перед MY. Даже сравнение одиночных символов расположит их в ал- фавитном порядке1). Например, Е стоит по алфавиту перед Y, и это можно установить, заметив, что символьный код Е (шест- надцатеричное С5) меньше символьного кода Y (шестнадцате- ричное Е)9)2). Это справедливо только для букв латинского алфавита. — Прим, перев. 2) Это справедливо независимо от того, выполняется ли команда знако- вого или беззнакового сравнения, поскольку левые биты символьных кодод всех букв одинаковы, что является важным свойством символьного кода.
Упорядочение по алфавиту 311 Для сравнения двух строк, содержащих более 2 символов, можно воспользоваться тем же алгоритмом. Мы сравниваем первые символы обеих строк, затем вторые символы, затем тре- тьи символы и т. д. Как только мы дойдем до пары различаю- щихся символов, мы основываем наше решение на сравнении этих символов. Так, сравнивая слова LEONINE и LEOPARD, мы основываем решение на сравнении четвертых букв, N и Р. Поскольку N стоит по алфавиту перед Р, LEONINE должно стоять перед LEOPARD. Как быть, если две строки имеют разную длину? Вы долж- ны сравнивать m символов, где m— меньшая из двух длин. Ес- ли все эти символы одинаковы, тогда более короткая строка ста- вится первой. Так, сравнивая BOUGH и BOUGHT, мы ставим сначала BOUGH, установив, что первые 5 символов обоих слов совпадают. Аналогично, сравнивая IRANIAN и IRAN, мы ста- вим сначала IRAN. Рассмотрим теперь процесс упорядочения по алфавиту как вариацию программы на Бейсике из предыдущей статьи. Един- ственная строка, которую надо изменить, это строка 30; теперь эта строка должна выражать следующее: «если T(J) стоит по алфавиту перед T(J+1) или если T(J)=T(J+1), перейти на строку 80». Остальная часть логического построения этой про- граммы останется без изменения. Это значит, что в программе на языке ассемблера (рис. 84.1) 6 предложений между метками ВЕТА и DECIDE, соответствую- щих строке 30 программы на Бейсике, надо заменить текстом, приведенным на рис. 85.1. В этой программе используется ряд специальных приемов, требующих пояснения. Основная идея заключается в использовании таблицы адре- сов. У нас по-прежнему есть последовательный массив Т 2-бай- товых величин, как на рис. 84.1; но теперь эти 2-байтовые ве- личины представляют собой адреса строк. Сами строки хранят- ся так же, как описано в статье 82, но мы не изменяем символы в строках — только адреса. После завершения программы у нас получится таблица адресов строк, расположенных в восходящем порядке по алфавиту (сами адреса не будет расположены в воз- растающем порядке; однако первый адрес, хранящийся в ячей- ках Т и Т+!1, будет адресом строки, которая стоит первой по алфавиту и т. д.). В программе используются ячейки ZP1 и ZP2 (каждая дли- ной 2 байта) на нулевой странице. Прежде всего выполняется сравнение длин двух строк, и меньшая длина пересылается в регистр X, являющийся счетчиком в цикле. Одновременно мы сохраняем флажок переноса (установленный командой сравне- ния) в знаковом разряде ячейки LFLAG. Это выполняется пу- тем циклического сдвига величины LFLAG вправо, в результате
312 Статья 85 LDA T,X ; Использование адресов STA ZP1 ; T(J) и T(J+1) из таблицы ; адресов LDA T+!1,X ; для установки значений STA ZP1+!1,X ; ячеек ZP1 и ZP2 нулевой LDA T+!2,X ; страницы, которые теперь STA ZP2 ; будут содержать начальные LDA T+!3,X ; адреса двух строк, STA ZP2+11 ; подлежащих сравнению LDY #!0 ; Начать с байтов длины LDA (ZP2),Y ; Сравнить два байта длины CMP (ZP1),Y ; для получения меньшей длины BCC SMALR ; и пересылки ее в регистр А LDA (ZP1),Y ; Отметить, что вторая строка SMALR ROR LFLAG ; короче (в знаковом разряде ; LFLAG находится 0) TAX ; Меньшая длина в регистре X CCHAR INY ; К следующему символу LDA (ZP2),Y ; Сравнить по одному символу CMP (ZP1),Y ; каждой строки. Если они BNE DECIDE ; не равны, следует принять ; решение DEX ; Выполнить это М раз (здесь BNE CCHAR ; М — меньшая длина) LDA LFLAG ; Если вторая строка короче, BPL EXCH ; порядок строк неверен DECIDE BCS GAMMA ; Если вторая >=, порядок ; строк верен EXCH (следующая команда) Рис. 85.1. Упорядочение по алфавиту массива строк. чего флажок переноса вдвигается в левый разряд ячейки LFLAG. Далее мы сравниваем М символов, где М определяет число шагов в цикле. Если не все символы одинаковы, мы, как и рань- ше, принимаем решение в точке DECIDE. Если все символы по- парно совпадают, мы имеем три случая: 1. Длины обеих строк были одинаковы. В этом случае стро- ки совпадают, и мы переходим на GAMMA. 2. Первая строка короче. В этом случае первая строка долж- на быть расположена перед второй; получается, что они распо- ложены в правильном порядке, и мы, как и в п. 1, переходим на GAMMA.
Поиск в упорядоченном массиве 313 3. Вторая строка короче. Заметьте, что в этом случае фла- жок переноса, который был ранее сохранен, оказывается сбро- шенным (в двух предыдущих случаях он был установлен). Вто- рая строка должна располагаться перед первой; строки стоят в неправильном порядке, и их следует поменять местами. Поэто- му, если содержимое LFLAG больше или равно 0, т. е. если флажок знака (сохраненный флажок переноса) сброшен, мы переходим на метку ЕХСН, где происходит перестановка. Упражнения 1. В программе на рис. 85.1 перед самой меткой CCHAR и как раз после определения меньшей длины, стоит команда ТАХ. Почему бы не получить меньшую длину сразу в регистре X, используя LDX и СРХ вместо LDA и СМР? 2. Почему, вместо того чтобы сохранять флажок перено- са командой ROR LFLAG, а затем использовать команды LDA LFLAG и BPL ЕХСН, нельзя написать РНР (сохранив весь ре- гистр Р, включая флажок переноса), а затем, позже, с помощью команды PLP восстановить регистр Р и организовать переход командой ВСС ЕХСН? 3. Представьте себе, что в четырех командах программы на рис. 85.1, непосредственно предшествующих метке SMALR, мы заменили ZP2 на ZP1, a ZP1 (две ссылки) на ZP2. а) Будет ли в точке SMALR определена та же самая ве- личина? б) Какая проблема возникает позже перед точкой DECIDE? Статья 86 Поиск в упорядоченном массиве Важным соображением в пользу хранения массивов в упо- рядоченном виде является то обстоятельство, что поиск в таком массиве осуществляется заметно быстрее, особенно если массив велик. Мы теперь покажем, как выполняется быстрый двоичный поиск, который мы кратко обсуждали в статье 49.
314 Статья 86 LDA N ; Установить начальное STA LAST ; значение LAST LDA ; Установить начальное STA FIRST ; значение FIRST LI LDA FIRST ; Найти среднее значение CLC ; FIRST и LAST, вычислив ADC LAST ; MIDDLE=(FIRST+LAST)/2 ROR ; Вдвинуть флажок переноса (если ТАХ ; MIDDLED* 127). Переслать ; в регистр X LDA Т—!1,Х ; Т (MIDDLE) <V? Если да, СМР V ; V входит во вторую ВСС L2 ; половину таблицы STX LAST ; Первая половина таблицы, BCS L3 ; поэтому изменить LAST и перейти L2 INX ; Вторая половина таблицы, STX FIRST ; поэтому изменить FIRST L3 LDA FIRST ; Выполнить все это снова, СМР LAST ; если только размер таблицы BNE LI ; не равен 1, т. е. FIRST = LAST LDX FIRST ; Т (FIRST) =V? Если нет, LDA T—!1,X ; V вообще не входит в СМР V ; таблицу. Если да, его BEQ L5 ; индекс в регистре X L4 (не найдено) L5 (найдено, индекс в регистре X) Рис. 86.1. Программа поиска в упорядоченном массиве. Предположим, что мы ищем некоторую величину V в мас- сиве с именем Т, который содержит элементы от Т(1) до T(N), причем N — переменная. Основная идея поиска заключается в делении’ таблицы пополам. На каждой стадии поиска мы рас- сматриваем только часть таблицы от Т (FIRST) до T(LAST). Первоначально это вся таблица, так что FIRST = 1, a LAST = = N. Таблица делится пополам путем нахождения среднего вели- чин FIRST и LAST; назовем это среднее MIDDLE. Посмотрим теперь, находится ли V в первой части этой таблицы между Т(FIRST) и Т(MIDDLE). Для этого проверим, выполняется ли условие V>T(MIDDLE); если выполняется, то в силу упорядо- ченности таблицы величина V должна быть во второй части таб- лицы (от Т (MIDDLE-)-1) до T(LAST), если, конечно, V вооб-
Поиск в упорядоченном массиве 315 ще входит в таблицу. В противном случае V находится в первой части таблицы. В любом случае FIRST и LAST нуждаются в изменении. Ес- ли V принадлежит первой части таблицы, от Т (FIRST) до Т(MIDDLE), то MIDDLE становится новым значением LAST, а FIRST остается без изменения. Если V принадлежит второй час- ти таблицы, от T(MIDDLE+1) до T(LAST), то MIDDLE+1 становится новым значением FIRST, a LAST остается без из- менения. Мы продолжаем делить таблицу пополам описанным обра- зом, пока размер таблицы не уменьшится до 1. Когда это прои- зойдет, FIRST и LAST должны совпадать. На этой стадии V ли- бо равно Т(FIRST), либо вообще отсутствует в таблице. На рис. 86.1 приведена программа выполнения двоичного поиска 8-разрядных величин без знака в таблице Т. Поскольку массив начинается с Т(1), смещения определяются так, как бы- ло описано в статье 23, и для обращения к Х-му элементу Т используется адрес Т—!1,Х, а не Т,Х. В программе предусмот- рено два выхода: если V не входит в таблицу, выполняется вы- ход через метку L4, если же V в таблице присутствует, програм- ма завершается в точке L5, причем индекс находится в реги- стре X. Вычисление среднего величин FIRST и LAST использует прием, который оказывается весьма полезным во всех случаях, когда определяется среднее. Конечно, мы складываем FIRST и LAST, а затем делим на 2 с помощью сдвига. Трудность заклю- чается в том, что сумма FIRST+LAST может оказаться боль- ше 255; если это произойдет, будет установлен флажок перено- са, в противном случае флажок переноса будет сброшен. Прием заключается в том, что деление на 2 выполняется с помощью команды ROR, а не LSR. В этом случае флажок переноса вы- двигается в левый разряд регистра А, что в данном случае и требуется. Вместо проверки условия > V>T(MIDDLE) мы проверяем условие Т (MIDDLE) <V, используя флажок переноса, как бы- ло предложено в статье 28. Программу можно легко преобразо- вать для поиска в двух параллельных массивах S и Т 16-разряд- ной величины, которая хранится в ячейках V и V+! 1. Для это- го надо заменить два сравнения на 16-разрядное сравнение, как описано в статье 29; с этой целью сразу за командой ТАХ мы пишем LDA S —!1, X СМР V+!l BNE DECIDE ; Сравнить Т (MIDDLE) ; с V, сначала ; старшие полуслова
316 Статья 86 (здесь метка DECIDE относится к команде ВСС, как в статье 29), а сразу за командой LDX надо написать LDA S — !1, X ; Сравнить Т (FIRST) СМР V-H1 ; с V, сначала BNE L4 ; старшие полуслова Цикл выполняется п раз для таблицы размера 2П. Для таб- лицы размера k число шагов цикла определяется как логарифм k по основанию 2, округленный до ближайшего целого; если это число п, то по фундаментальным свойствам логарифмов 2п-1< <&<2П. (В дальнейшем изучении техники вычислений вы весь- ма часто будете сталкиваться с логарифмами по основанию 2; например, существуют методы сортировки п чисел за п log2 п шагов. См. также таблицу логарифмов в упражнениях к статье 49.) Замечательным обстоятельством является тот факт, что рассматриваемый программный цикл всегда выполняется за 42 машинных цикла независимо от того, выполняется ли переход на метку L2 командой ВСС или нет, если только команда BNE действительно передает управление в точку L1. Для таблицы размера 2П полное число машинных циклов составляет 42п-)-27 плюс еще один цикл, если V действительно входит в таблицу. Можно заметить, что программу можно сократить, удалив из нее строку LI LDA FIRST (и переместив метку L1 ниже, к команде CLC). Это возможно потому, что каким бы путем мы не пришли в точку L1—через STA FIRST, или переходом BNE L1, значение FIRST уже находится в регистре А (в последнем случае команда LDA FIRST выполняется'перед BNE L1). Уда- ление строки уменьшит время выполнения до 38п+27 машин- ных циклов. Дальнейшие улучшения программы рассматрива- ются в упражнениях. Описанный способ поиска в упорядоченной таблице удобен, если сама таблица не изменяется. Если же таблица модифици- руется в ходе выполнения программы, особенно если с помощью этой программы она составляется, такой способ может оказать- ся неэффективным, так как сохранение упорядоченности табли- цы в процессе ее роста требует значительного машинного вре- мени. Упражнения 1. Рассмотрите строку с меткой L3 на рис. 86.1. Можно ли команду LDA заменить на ТХА? Объясните. 2. Можно ли в программе на рис. 86.1 поместить метку L1 на строку с командой ADC? Почему можно или почему не- льзя?
Двумерные массивы 317 >]< 3) Можно ли в программе на рис. 86.1 удалить команду LDX FIRST? Почему можно или почему нельзя? Статья 87 Двумерные массивы Во многих версиях Бейсика допустимо использование эле- ментов массива типа T(I,J), если массив Т был объявлен пред- ложением DIM T(m,n) для некоторых целых т и п. То же спра- ведливо и для Фортрана, хотя оператор DIM заменяется в этом случае на DIMENSION (или INTEGER, REAL и др.). Такой, массив называется двумерным. Если у нас есть двумерный массив размера mXn элементов, то полное число элементов в массиве составит тп. В программе на языке ассемблера мы резервируем место под все эти элемен- ты с помощью псевдокоманды DFS точно так же, как и для од- номерных массивов. Например, для организации массива Т раз- мером 10X15 1-байтовых величин мы должны написать Т DFS 1150 (поскольку 150=10X15). Возникает, однако, вопрос: как обратиться к элементу T(I,J) ? Мы не можем, например, загрузить I в регистр X, a J — в ре- гистр Y; мы должны вычислить единую величину и загрузить ее либо в регистр X, либо в регистр Y. Что это за величина, и как она вычисляется? Существуют два хорошо известных спосо- ба ее вычисления, один из которых основан на правилах языка Фортран, а другой — на правилах языка ПЛ/1. В языке ПЛ/1 элементы массива Т размером тХп располагаются в памяти в следующем порядке: Т(1,1), Т(1,2) и т. д. до Т(1,п); затем Т(2,1); затем Т(2,2) и т. д. до конца массива. В языке Фортран, однако, порядок иной: Т(1,1), Т(2,1) и т. д. до Т(/и,1); затем Т(1,2); затем Т(2,2) и т. д. Таким образом, в языке Фортран порядок элементов соответствует их расположению в столбцах двумерного массива, в то время как в языке ПЛ/1 порядок эле- ментов следует их расположению в строках этого массива (рис. 87.1). Мы говорим, что в Фортране массив развертывается по столбцам двумерной матрицы, а в ПЛ/1—по строкам.
318 Статья 87 а 6 Рис. 87.1. Представление в памяти двумерного массива: а — развертывание по столбцам, б — развертывание по строкам.
Двумерные массивы 319 В программе на языке ассемблера мы можем с равным успе- хом использовать оба представления массива. Пусть мы только что определили I и J, и хотим использовать команду вроде LDA Т,Х для загрузки элемента T(I,J). Величина, которую нам надо вычислить и переслать в регистр X (для массива размером тХп), имеет вид (I — 1)>]<»+ (J— 1) (развертывание по строкам),. (J—l)*m(I—1) (развертывание по столбцам). Величина в регистре X является индексом элемента T(I,J); та- ким образом, мы получили формулы для вычисления индекса. Рассмотрим процесс вычисления индекса. При развертывании массива по строкам для элемента Т(1,1) мы имеем 1=1, J = 1 и (I—l)>]<n+(J —1)=0. Это правильное значение индекса. Дей- ствительно, если регистр X содержит 0, то команда LDA Т,Х загружает Т, или, иными словами, первый байт массива Т, т. е. элемент Т(1,1). В общем случае для элемента T(1,J) мы имеем (I—l)*n+(J—1)=J—1; если, например, J=5, то индекс ра- вен 5—1, т. е. 4. Это опять правильное значение индекса, по- скольку ячейка с адресом T-f-!4 содержит элемент Т(1,5). Для элемента Т(2,1) мы имеем 1=2, J = 1 и (I— 1)>|<п+ (J — — 1)=п. Первая строка от Т(1,1) до Т(1,4) занимает первые п ячеек массива, поэтому следующая ячейка имеет адрес Т+п. Снова мы получили пр1авильное значение индекса. Проводя ана- логичные рассуждения, можно убедиться, что формула (I—l)>fc >}<n+(J—1) верна для всех элементов. При развертывании массива по столбцам для элемента Т(1,1), мы имеем 1 = 1, J = 1 и (J— l)>|cm-|-(I —1) =0. Это, как уже бы- ло рассмотрено, правильное значение индекса. В общем случае для элемента Т( 1,1) теперь мы имеем (J—1)>|</п-|-(I—1) =1 —1; если, например, 1=7, то индекс равен 7— 1, т. е. 6. Это правиль- ное значение, поскольку ячейка с адресом T-f-16 содержит элемент Т(7,1). Для элемента Т(1,2) мы имеем 1 = 1, J=2 и (J—1)(I—1) =т; первый столбец от Т(1,1) до Т(/п,1) занимает первые т ячеек массива, поэтому следующая ячейка имеет адрес T-f-m. При желании рассуждения можно продол- жить и проверить правильность формулы для всех элементов. Обе приведенные формулы можно обобщить разными спосо- бами. Если Т — последовательный массив ^-байтовых (а не 1-байтовых) величин размером тХп, тогда все индексы следует умножить на k. Если Т начинается с элемента Т(0,0), (а не Т(1,1), как было раньше), формулы для вычисления индекса будут иметь вид I*n+J (развертывание по строкам), (развертывание по столбцам).'
320 Статья 87 Эти формулы отличаются от первоначальных только тем, что в них не вычитается 1. Нетрудно догадаться, что в формулах вычитается та самая 1, которая стоит в обозначении первого эле- мента Т(1,1). Может показаться, что вычисление индекса в процессоре 6502 будет выполняться недопустимо медленно из-за необходи- мости умножения. Однако умножение на константу (пг или п) легко осуществить, образовав таблицу кратных этой константе и проводя затем поиск в таблице, как было описано в статье 36. Формулы для массива, начинающегося с Т(1.1), можно пред- ставить в следующем виде: I^n+J — (п+1) (развертывание по строкам), J+m + I—(тп+1) (развертывание по столбцам). Константы п+1 или пг+1 можно вычитать'из Т, как было опи- сано в статье 23, поэтому вычислять и пересылать в индексный регистр надо только величины Isjcn+J или J+m+I. Например, для массива размером 10X10 п+1=пг+1 = 11, поэтому обра- щение к элементу T(I,J) будет выполняться по адресу Т— 111,X (LDA Т—!11,Х), если в регистре X содержится величина I+n+J (или соответственно J>|i/n+I). Упражнения 1. Напишите предложения Бейсика (для 8-разрядных дан- ных), эквивалентные приведенным ниже последовательностям команд на языке ассемблера. Пусть Т — массив размером 10Х Х10 1-байтовых величин, развертываемый по столбцам и начи- нающийся с T(l,l); TENS — это 10-байтовая таблица кратных числа 10 от TENS(l) = 10 до TENS(10) = 1001>. * (а) LDA Т+137 STA и (б) LDX L LDA CLC TENS —11, X ADC ТАХ К LDA V STA Т-!11, X *) Удостоверьтесь, что в ваших предложениях нет выражений вида Т(е). Не забудьте, что Т — это двумерный массив, и обращение к его элементам возможно только в форме T(f, g).
16-разрядные числа со знаком 321 2. Напишите программу для процессора 6502, выполняющую операцию T(I,J)=T(I,J) —1. Воспользуйтесь командой DEC с индексной адресацией и не обращайтесь к подпрограмме MULT (статья 39). Вместо этого используйте таблицу TENS, как в предыдущем упражнении. Пусть Т — массив размером 10X10 1-байтовых величин, начинающийся с Т(1,1). Массив разверты- вается: а) по столбцам; б) по строкам. 3. Пусть для обращения к элементу T(I,J) массива Т (см. упр. 1) было вычислено и помещено в регистр X значение индекса, который предполагается использовать в некоторой команде Z. Теперь понадобилось обратиться к элементу T(I,J+ + 1) с помощью той же команды. Как надо изменить содержи- мое регистра X, если массив развертывается: а) по строкам; б) по столбцам? Статья 88 16-разрядные числа со знаком Целые числа, используемые в программах на Бейсике, всег- да являются числами со знаком и обычно имеют длину 16 раз- рядов. 16-разрядные целые со знаком можно использовать и в программах на языке ассемблера, если развить уже изученные нами способы работы со знаковыми числами. В статье 7 было упомянуто, что сложение и вычитание 8-раз- рядных величин со знаком не отличается от аналогичных дей- ствий с 8-разрядными величинами без знака. Посмотрим, поче- му это так. Пусть Р и Q — d-разрядные числа со знаком, а Р'и Q' — соответствующие им значения без знака (если Р^О, то Р=Р; если Р<0, то P'=P+2d; аналогично для Q). Результат сложения Р' и Q' (назовем его S) есть P'+Q', если флажок переноса сброшен, и P'4-Q'+2d, если флажок переноса установ- лен. Докажем, что если R=P+Q и R'— соответствующее R 21—589
322 Статья 88 значение без знака, to R'=S . Всего могут встретиться шесть случаев: - Знак Р + + + — — —— Знак Q + — . — + + — Знак R + + — + — — Перенос 0 1 0 1 0 I Р'= P P р P + 2d P + 2d P + 2d Q' = Q Q+2d Q + 2d Q Q Q + 2d R' = R R R + 2d R R+2d R+2d P'+Q'= R R+2d R + 2d R+2d R+2d R+2-2d S = R R R+2d R R+2d R + 2d Видно, что во всех случаях R'=S. Обратите внимание, что результат не зависит от значения d. То, что справедливо для 8-разрядных величин, справедливо и для 16-разрядных; другими словами, 16-разрядные величины можно складывать по правилам, изложенным в статье 15, и вы- читать по правилам, изложенным в статье 17. Это, однако, несправедливо для умножения, что можно по- яснить даже на примере 8-разрядных величин; если мы умно- жаем а на —Ь, мы в действительности умножаем а на 256—6 (без знака) и получаем 256а—ab, в то время как хотели бы получить 2562—ab (что является представлением — ab, как 16- разрядной величины, в форме дополнения до двух). Умножение величин со знаком а и b в общем случае надо выполнять следующим образом: 1) установить с равным ИСКЛЮЧАЮЩЕМУ ИЛИ знаков а и b (в результате с=0, если и только если а и b имеют оди- наковые знаки); 2) установить а=а. Если а отрицательно, установить а= = — а; 3) установить <р=6. Если b отрицательно, установить р= = -6; 4) умножить а на р, получив у; 5) если с=0, результат есть у; если с=1, результат есть —у, (тот же алгоритм можно использовать и при делении). Получение отрицания 16-разрядной величины со знаком вы- полняется так же, как и для 8-разрядной величины со знаком; мы вычитаем ее из нуля (как 16-разрядную величину). Сравнение двух 16-разрядных чисел со знаком начинается со сравнения левых полуслов двух чисел, как 8-разрядных величин со знаком. Можно использовать способы, изложенные в статьях 54 или 56. Если левые полуслова равны, сравниваются правые полуслова, как величины без знака. (Вспомните, что левый раз-
16-разрядные числа со знаком 323 ряд правого полуслова 16-разрядной величины со знаком не вы- полняет функцию знакового разряда.) Преобразование 8-разрядной величины со знаком в 16-раз- рядную величину со знаком выполняется путем образования ле- вого полуслова 16-разрядной величины, состоящей для положи- тельной величины из 8-разрядных нулей, а для отрицательной— из 8 двоичных единиц ($FF), как это было упомянуто в ста- тье 12. Сложение 8-разрядной величины со знаком U с 16-разрядной величиной со знаком V можно выполнить без преобразования, описанного в предыдущем абзаце. Предположим, что мы при- бавляем U, как величину без знака, к V (см. статью 19). Если U представляет собой отрицательное число —k, тогда мы вы- числяем V-f-256 — k, в то время как нам надо получить V—k, т. е. на 256 меньше. Это можно исправить, предварительно про- веряя знак U и, если U отрицательно, вычитая 1 из левого по- луслова V. Тем самым мы уменьшим V на 256; после этого мож- но продолжить, руководствуясь правилами из статьи 19. Про- грамма, выполняющая щим образом: указанные операции, выглядит следую- LDA U Загрузить 8-разрядную величину U BPL L7 Если она отрицательна, уменьшить DEC V-M1 V на 256 (байты V переставлены) L7 CLC Теперь прибавить U ADC V к правому полуслову V ВСС L8 Если возник перенос, INC V+ll прибавить 1 к левому полуслову V L8 STA V Записать новое правое полуслово V Аналогичной методикой можно воспользоваться для обработ- ки «-байтовой величины со знаком при п>2. В частности, при сравнении двух таких величин мы сравниваем их левые байты, как 8-разрядные целые со знаком, и затем при необходимости все остальные байты, как 8-разрядные целые без знака. Упражнения 1. Чему равно минимальное 16-разрядное целое число со знаком? Чему равно максимальное 16-разрядное целое число со знаком? 'Выразите эти величины в виде десятичных целых чисел, а не в виде выражений со степенями двух. 2. Напишите программу для сравнения двух 16-разрядных чисел со знаком U и V и перехода на метку LESS, если U<V. Используйте способ знакового сравнения из статьи 56. 21*
324 Статья 89 3. Напишите программу для вычитания 8-разрядного цело- го со знаком U из 16-разрядной величины V (с переставленны- ми байтами), не прибегая к преобразованию U в 16-разряднук> форму. Используйте вариацию способа, проиллюстрированного в последней программе этой статьи. Статья 89 Отрицательное индексирование В статье 22 было показано, что циклы, .в которых содержи- мое регистра X уменьшается от N до 0, выполняются быстрее, чем циклы, в которых содержимое регистра X возрастает от 6 до N. Это происходит потому, что последовательность команд- DEX—BNE требует меньше времени для выполнения, чем по- следовательность INX—СРХ—BNE. Существует прием, назы- ваемый отрицательным индексированием, который позволяет ускорить выполнение циклов, в которых по каким-либо причи- нам значение индекса должно возрастать от 0 до п (п должно быть константой). Для того чтобы величины в регистрах X и Y можно было использовать для индексирования, их следует рассматривать как величины без знака. Вполне возможно загрузить в регистр X число со знаком —1; но, имея двоичное представление 11111111, оно будет рассматриваться1) как число без знака 255. Если бы у вас был массив Т с элементами от Т ( — 5) до Т (5) (что до- пускается, например, языком Паскаль) и вы поместили —1 в регистр X, то команда LDA Т,Х не загрузила бы элемент Т(—1); вместо этого она попыталась бы загрузить отсутствую- щий элемент Т (255). Отрицательное индексирование позволяет использовать в качестве индексов отрицательные числа и обойти в то же время отмеченные выше трудности с помощью специальным образом подобранных смещений. Мы проиллюстрируем этот прием на *> Процессором — Прим, перев.
Отрицательное индексирование 325 примере массива Т, имеющего элементы от Т (1) до Т (6) (п=6). Прежде всего посмотрим, что произойдет, если мы начнем с элемента Т (6) и будем двигаться вниз до Т (1). Последова- тельные значения индекса в регистре X будут 6, 5, 4, 3, 2 и 1. При использовании отрицательного индексирования последова- тельные значения содержимого регистра X окажутся —6, —5, —4, —3, —2 и —1. Процессор 6502 воспринимает их, как если бы это были числа 250, 251, 252, 253, 254 и 255. Рассмотрим первый из этих индексов, именно 250. Когда ре- гистр X содержит 250, мы хотим, чтобы он указывал на элемент Т (1), который можно было бы загрузить в регистр А командой LDA Т, так как это первый байт массива Т. Этого же можно достичь командой LDA Т—!250,Х, поскольку адрес Т минус 250 плюс 250 — это просто адрес Т. Второй индекс равен 251, и, когда он находится в регистре X, мы хотим обратиться к элементу Т (2), который хранится в ячейке Т+! 1. Вместо команды LDA T-f-!l опять можно восполь- зоваться командой LDA Т—1250,X, поскольку адрес Т минус 250 плюс 251—это адрес Т плюс 1. Легко видеть, что команда LDA Т — 1250,X будет всегда загружать в регистр А элемент Т (&), если &-й индекс находится в регистре X. Рассмотрим практическое воплощение описанной идеи. В приведенной ниже программе выполняется поиск среди эле- ментов массива от Т (1) до Т (6) первого байта T(J), равного В: LDX #!0 ; Начать с первого байта Т LDA В ; Величина В постоянно ; в регистре А LOOP СМР Т, X ; В равно Т(Х+1)? BEQ FOUND ; Если да, конец INX ; К следующему байту Т СРХ #!6 ; Повторить, пока в регистре X BNE LOOP ; не будет 6 (было 5, обращение ; к Т+!5) С использованием отрицательного индексирования програм- ма будет выглядеть следующим образом: LDX =8=1256—16 LDA В LOOP СМР Т-1250, X BEQ FOUND INX BNE LOOP ; Начать с первого байта Т ; Величина В всегда в регистре А ; В равно Т(Х-250+1)? ; Если да, конец : К следующему байту Т ; Повторить, если в per. X не 0
326 Статья 89 Заметьте, что теперь в цикле не 5 команд, а только 4. Это ос- новное достоинство отрицательного индексирования — возмож- ность изъять из цикла команду СРХ (или CPY) и,,поскольку команда INX (или INY) устанавливает флажок нуля, непосред- ственно вслед за ней поставить команду BNE, как это делалось с командами DEX или DEY. Отрицательное индексирование нельзя использовать, если число шагов п определяется переменной, а не константой, пото- му что адресное выражение Т+«—!256 законно, только если п — константа. Вообще отрицательное индексирование следует применять лишь в тех случаях, когда время выполнения про- граммы является критическим фактором и экономия в каждом шаге двух машинных циклов (от команды сравнения) представ- ляется необходимой. Используя отрицательное индексирование легко ошибиться, поэтому им не следует увлекаться. Приведем несколько' примеров возможных ошибок. Работая с ассемблером LISA 1.5, нельзя написать в начале цикла LDX*!—6 (ассемблер LISA 2.5 допускает такую запись). Если первоначально команда INX была в начале вашего цикла, те- перь ее следует переставить в конец, и надо проследить за тем, чтобы индексы и начальное содержимое регистра X не разош- лись бы на 1. Выражение Т—!250 в команде СМР в нашем при- мере можно записать в виде T-J-I6— !256, чтобы выделить кон- станту 6; но нельзя использовать выражение Т—!256+!6, кото- рое интерпретируется программой LISA как Т—(1256+16), что, конечно, дает другой результат. Вообще следует избегать двух или более членов в адресных выражениях, если хотя бы один из них (кроме самого последнего) имеет знак минус. Упражнения 1. Пусть нам надо удалить элемент Т (50) из массива Т, со- держащего 200 байтов. Для этого надо переслать Т (51) на ме- сто Т (50); затем Т (52) на место Т (51) и т. д. до Т (200), кото- рый пересылается на место Т (199). Соответствующая программа выглядит так: LDY #!50 LOOP INY LDA T, Y STA T— !1, Y CPY *1200 BNE LOOP + а) Переделайте программу так, чтобы команда INY сто- яла непосредственно перед CPY. Не увеличивайте при этом об-
Паритет при работе с магнитной лентой 327 щее число байтов в цикле. (Проведите ручную прогонку вашей программы, чтобы убедиться в полной эквивалентности обеих программ.) б) Переделайте ту же программу, использовав отрица- тельное индексирование. Команду LDY напишите в виде LDY #1256—п. (Как и в п. а), проведите тщательную ручную прогонку программы.) в) Сколько при этом экономится байтов и машинных циклов? 2. Если цикл из упр. 1 заканчивается командами DEY и BNE, его нельзя переделать так, чтобы обработка массива вы- полнялась .в нисходящем порядке от Т (200) до Т (51). Почему? (Если это для вас не очевидно, проведите ручную прогонку пер- вых двух-трех шагов цикла.) 3. Представьте себе цикл, с помощью которого в массиве Т ищется элемент Е. Если Е не равно никакому Т (Л) для любого fe(l^^^N), программа увеличивает N на 1 и устанавливает для нового значения N T(N) = E. Можно ли улучшить такую программу использованием отрицательного индексирования? По- чему можно или почему нельзя? Статья 90 Паритет при работе с магнитной лентой Запись на магнитную ленту и считывание с нее не выполня- ются с абсолютной надежностью. Время от времени бит, имею- щий значение 1, в процессе передачи получит значение 0 (это называется выпадением бита), или бит, имеющий значение 0, примет значение 1 (это называется вклиниванием бита). Мы теперь обсудим использование в процессоре 6502 старого приема, позволяющего автоматически корректировать ошибки такого рода, если они не возникают слишком часто. Прием из- вестен уже свыше 20 лет; он называется контролем поперечного и продольного паритета. Будем считать, что мы записываем на ленту группу из п байт, называемую блоком. Если требуется записать более
328 Статья 90 п байт, они образуют несколько блоков, каждый длиной п байт. Описываемый прием позволяет корректировать все вклинивания и выпадения битов, если только в каждом блоке встречается не более одной такой ошибки. Число п не фиксировано, его можно устанавливать в соот- ветствии с надежностью ленты. Например, 1000 байт можно за- писать как 10 блоков по 100 байт каждый (п = 100). Если, од- нако при этом слишком часто возникает более одной ошибки в Разряды поперечного паритета ♦ ♦ ♦ ♦ ♦ Разряды продольного паритета Раз- -*• ряды -► данных-** г Межблочный • промежуток Рис. 90.1. Расположение разрядов на ленте. блоке, мы можем попробовать записать те же 1000 байт как 100 блоков по 10 байт каждый (п = 10). Представьте себе байты в блоке записанными на ленте так, как это показано на рис. 90.1. Мы определяем значения допол- нительного ряда и дополнительной колонки двоичных разрядов и записываем их вместе с каждым блоком. При считывании информации с ленты проверка этих дополнительных разрядов . рядов и колонок позволяет корректировать ошибки. Разряды дополнительного ряда содержат биты поперечного паритета. Каждый из этих разрядов заполняется таким образом, чтобы суммарное число битов со значением 1 в любой колонке j было всегда нечетным. Разряды в дополнительной колонке со- держат биты продольного паритета; каждый из них заполняется так, чтобы суммарное число битов со значением 1 в любом ряду было всегда нечетным. Эти числа делаются именно нечетными, чтобы в каждой ко- лонке и в каждом ряду был бы по крайней мере один бит. со значением 1. В некоторых ЭВМ, особенно устаревших (не за- будьте, что мы описываем старый прием), фактически на ленту записываются только биты, имеющие значение 1, а биты, имею- щие значение 0, не записываются; в таких случаях наличие в каждой колонке хотя бы одного бита со значением 1 помогает синхронизировать лентопротяжный механизм. Если какая-либо строка (ряд или колонка) содержит нечет- ное число битов со значением 1, мы говорим, что паритет этой строки нечетный, или паритет равен 1. В противном случае па-
Паритет при работе с магнитной лентой 329 ритет строки четный, или паритет равен 0. Паритет ряда или колонки можно найти так, как мы это делали в статье 35, под- считывая биты со значением 1; однако существует более простой способ, основанный на том факте, что ИСКЛЮЧАЮЩИЕ ИЛИ группы битов всегда дает паритет этой группы (0 или 1). Про- исходит это потому, что биты со значением 0 не влияют на ре- зультат операции ИСКЛЮЧАЮЩЕЕ ИЛИ, а биты со значени- ем 1 изменяют этот результат с 1 на 0 или с 0 на 1 в точности так же, как последовательные целые числа, 1, 2, 3 и т. д., явля- ются попеременно то нечетными, то четными. Для того чтобы образовать байт продольного паритета, мы выполняем операцию ИСКЛЮЧАЮЩЕЕ ИЛИ над всеми пере- сылаемыми байтами; затем, поскольку мы хотим, чтобы суммар- ное число битов со значением 1 в каждом ряду было нечетным, мы выполняем еще одну операцию ИСКЛЮЧАЮЩЕГО ИЛИ с восемью 1 ($FF). В результате мы получаем паритет пересы- лаемого блока байтов. Для того чтобы определить биты попе- речного паритета, мы выполняем операцию ИСКЛЮЧАЮЩЕГО ИЛИ над всеми битами каждого байта (и, в частности, над би- тами продольного паритета) вместе с одним дополнительным битом со значением 1 для получения нечетного суммарного чи- сла битов. Предположим теперь, что один из битов этого блока либо выпал, либо вклинился. Пусть это бит в ряду х и колонке у. Тогда при чтении ленты ряд х будет иметь ошибочный паритет; паритет этого ряда будет 0, а не 1. Аналогично колонка у будет иметь ошибочный паритет. Паритеты всех остальных рядов и колонок будут правильными. Отсюда видно, как можно использовать контроль паритета для исправления ошибок. Считывая информацию ленты, мы про- веряем паритеты всех рядов и колонок. Если все они правильны, мы считаем, что блок не содержит ошибок. Если один ряд и одна колонка имеют ошибочные паритеты (скажем, ряд х и ко- лонка у), мы находим бит, принадлежащий ряду х и колонке у, и изменяем его значение (на 0, если была 1, и на 1, если былО). Важно отметить, что ошибочный бит может принадлежать битам паритета. Это, однако, не меняет дела, так как биты поперечного паритета сами образуют ряд, который может быть рядом х; аналогично биты продольного паритета образуют ко- лонку, которая может быть колонкой у. Если в блоке две ошибки, тогда два ряда или две колонку или и два ряда, и две колонки будут иметь ошибочные пари- теты. В этом случае мы не можем исправить ошибку, но по, крайней мере можем установить факт наличия ошибки. По этой причине описанный метод называют методом исправления одш. ночной и обнаружения двойной ошибки.
330 Статья 90 Упражнения 1. Приведенная ниже программа определяет паритет байта в регистре А и заносит его в правый разряд этого регистра: STA В LDX #!7 LOOP LSR EOR В DEX ; BNE LOOP а) Сколько байтов (включая байт данных В) и сколько машинных циклов требует эта программа? sfc б) В каком разряде какого регистра хранится промежу- точный результат (ИСКЛЮЧАЮЩЕЕ ИЛИ первых бит)? Объ- ясните. (Подсказка: проведите ручную прогонку программы с числом % abcdefgh в регистре А, задав битам от а до h произ- вольные значения — или 0, или 1. Следите за содержимым каж- дого разряда регистра А в процессе ручной прогонки, используя черточку для обозначения операции ИСКЛЮЧАЮЩЕЕ ИЛИ; например, а—b—с обозначает ИСКЛЮЧАЮЩЕЕ ИЛИ битов а, b и с.) 2. Приведенная ниже программа также заносит паритет бай- та в регистре А в правый разряд этого регистра: STA В LSR В LOOP EOR В LSR В BNE LOOP а) Сколько машинных циклов (максимум и минимум) и сколько байтов (.включая байт данных В) требует эта про- грамма? б) Ответьте на п. б) упр. 1 для этой программы. 3. Пусть паритет блока, записанного на ленту, помещен в 8-разрядное слово BW; паритет того же блока, считанного с ленты, помещен в 8-разрядное слово BR. Как проще всего найти ошибочный бит в BR, если BR^=BW? Задача 5 для решения на ЭВМ Формирование абзаца Напишите программу, которая вводит с клавиатуры абзац текста, содержащий не более 256 символов; заносит эти символы в массив; выводит массив на экран так, чтобы на каждой строке было не более 27 символов. При этом не допускается переносить
Дисковая операционная система ЭВМ Эпл 331 слова, за исключением случаев, когда в составе слова имеется дефис. Так, если введено *>: FROM NORTH AMERICA, IT’S EASY TO WING YOUR WAY SOUTH TO THE CARIBBEAN. TRAVEL AGENTS WHO KEEP UP-TO-THE-MINUTE SCHEDULES CAN INFORM YOU ABOUT SPECIAL STOPOVER PRIVILEGES. то программа должна вывести FROM NORTH AMERICA, IT’S EASY TO WING YOUR WAY SOUTH TO THE CARIBBEAN. TRAVEL AGENTS WHO KEEP UP-TO-THE- MINUTE SCHEDULES CAN INFORM YOU ABOUT SPECIAL STOPOVER PRIVILEGES. Первая строка имеет длину 24 символа, но вы не можете рас- положить на ней две первые буквы слова EASY, потому что по условию переносить слова нельзя; следующая строка содержит точно 27 символов и т. д. Слово UP-TO-THE-MINUTE можно перенести на любом дефисе. В качестве дополнительного задания вы можете воспользо- ваться приемами работы с длинными массивами для придания вашей программе возможности обрабатывать абзацы длиной более 256 символов. Ваша программа должна, естественно, использовать подпро- грамму GETLNZ, а не RDKEY, поскольку GETLNZ предостав- ляет возможность исправить ошибки в процессе ввода строки. Статья 91 Дисковая операционная система ЭВМ Эпл В статье 51 уже говорилось о командах дисковой операци- онной системы Эпл (ДОС Эпл) BSAVE и BLOAD. В состав ДОС входят и другие команды, которыми можно пользоваться !> Из Северной Америки нетрудно отправиться на самолете на юг к Ка-» рибскому морю. Служащие трансагентства, всегда имеющие последние све- дения о графиках полетов, могут проинформировать вас о предоставляемых вам возможностях остановок в пути.— Прим, перев.
332 Статья 91 при программировании на Бейсике и языке ассемблера. Напри- мер, команда DELETE f удаляет файл f с диска; эта команда может оказаться полезной, если вам не хватает места на дис- кете. Возможно, вы познакомились с некоторыми командами ДОС в процессе изучения языка Бейсика ЭВМ Эпл. Мы, однако, не будем на это рассчитывать и дадим краткий обзор некоторых наиболее употребительных команд ДОС. Полный список команд ДОС содержится в справочном руководстве по ДОС Эпл. Дисковая операционная система работает с файлами на ди- ске. Файл содержит данные, которые хранятся на диске, но в нужный момент могут быть перенесены в оперативную память (загружены). Как мы уже видели, файлам присущи имена, и любой файл на диске в обычных условиях должен иметь имя, отличное от имен всех остальных файлов на диске. Команды BSAVE и BLOAD используются с двоичными фай- лами. Двоичный файл можно записать на диск или загрузить в память непосредственно из программы на языке ассемблера LISA. Делается это с помощью указания в тексте программы на ассемблере тех же команд, которыми мы пользуемся в системах LISA или Бейсик. Если, например, надо выполнить команду DBSAVE LDATA, А$8С00, L$200 (чтобы записать в файл с именем LDATA 512 (шестнадцатеричное 200) байт, начиная с адреса 8100, см. статью 51), следует написать: LDX #$FF ; Начальное значение индекса ; символа —1 LOOP INX ; Увеличить индекс символа LDA BSMSG, X ; Получить следующий символ СМР #CRET ; Это возврат каретки? BEQ DONE ; Если да, конец JSR COUT ; Если нет, вывести символ JMP LOOP ; И получить следующий DONE JSR COUT ; Вывести возврат каретки В программу, как обычно, надо включить определения CRET EQU $8D и COUT EQU $FDED, а также строки BSMSG BYT $84 ; CONTROL-D ASL "BSAVE, LDATA, A$8C00, L$200" BYT CRET ; CARRIAGE RETURN ; (возврат каретки) Заметьте, что после символа А должно стоять десятичное или шестнадцатеричное число. Суть здесь заключается в том, что если вводимая строка начинается с символа control-D, все осталь-
Дисковая операционная система ЭВМ Эпл 333 ные символы до возврата каретки рассматриваются ЭВМ. Эпл как команда ДОС. В общем случае строка символов, завершаю- щаяся возвратом каретки, называется записью. Программа GETLNZ, например, вводит одну запись. ДОС Эпл оперирует как с двоичными, так и с текстовыми «файлами, составленными из записей. Если, однако, двоичный файл записывается и загружается целиком, текстовой файл за- писывается и считывается символ за символом. Это осуществля- ется с помощью тех же подпрограмм монитора, которые мы ис- пользуем для ввода символов с клавиатуры или вывода их на экран. При этом сначала мы должны указать системе Эпл, с каким файлом мы работаем. Начиная работать с некоторым файлом с именем fname, мы должны открыть его и определить вид операции: запись или чтение. Команда ДОС OPEN fname открывает файл; команда ДОС READ fname определяет, что файл будет читаться; команда ДОС WRITE fname определяет, что файл будет записываться. Открытие файла представляет собой операцию инициализации; она выполняется один раз в начале любой программы, которая будет работать с файлом. Заметьте, что команды READ и WRITE в действительности не осуществляют чтение и запись. Эти операции выполняются подпрограммами монитора. Например, подпрограмма RDKEY вводит один символ; подпрограмма COUT выводит один сим- вол; подпрограмма GETLN вводит одну строку во входной бу- фер начиная с адреса 0200. Мы также использовали подпро- грамму GETLNZ, которая выводит возврат каретки и вызывает GETLN. Это удобно при работе с дисплеем, так как вводимая строка высвечивается на экране начиная с его левого края, но при вводе с диска в этом нет необходимости, и вместо GETLNZ используют GETLN. Если вы работаете с несколькими файлами, начните про- грамму с команды MAXFILES п, где п — количество используе- мых вами файлов (п^Э)1). Затем откройте каждый файл. Ко- манды READ и WRITE (без OPEN) будут теперь управлять чтением и записью файлов. Если вы считывали файл а и хотите считать файл р, дайте команду READ р, так же и для записи. Работаете ли вы с одним файлом, или несколькими, вы долж- ны в конце вашей программы закрыть все файлы командой ДОС CLOSE. Команда CLOSE fname закрывает файл с именем fname. Ко всем упомянутым выше командам ДОС можно обра- щаться из программ на языке ассемблера LISA так же, как это было показано в начале статьи применительно к команде BSAVE. » Не пользуйтесь этой командой в системе LISA.
334 Статья 91 Если вы используете подпрограммы RDKEY и COUT для ра- боты с диском, вы можете одновременно использовать KEYIN для ввода с клавиатуры и COUT1 для вывода на экран. Под- программы KEYIN и COUT1 действуют точно так же, как RDKEY и COUT, только они не работают с диском. Для них требуются следующие предложения EQU: KEYIN EQU $FDIB COUT1 EQU $FDF0 Упражнения 1. Измените программу, описанную в этой статье так,, чтобы получить подпрограмму DOSCOM для выполнения ко- манд ДОС. Выполнение требуемой команды осуществляется за- грузкой адреса строки с этой командой в регистры А и X (стар- шую половину в регистр А, младшую — в X) и вызовом DOSCOM. Так, например, последовательность строк LDA /BSMSG LDX #BSMSG JSR DOSCOM приведет к выполнению команды BSAVE LDATA, А$8С00, L$200 (фрагмент программы, начинающийся с метки BSMSG, дан в тексте статьи). Используйте постиндексную косвенную ад- ресацию с двумя ячейками на нулевой странице ZP и ZP1; не заботьтесь о сохранении и восстановлении каких-либо регистров. Считайте, что CRET и COUT определены, как обычно. 2. Выполните упр. 1, использовав вместо постиндексной косвенной адресации модификацию адреса. Не пользуйтесь ре- гистром Y. Используйте обозначение MODIFY. 3. а) Что выполняет приведенная ниже программа (вся про- грамма, а не отдельные команды), если считать, что DOSCOM описана выше, обозначения CRET, RDKEY и COUT определены* как обычно, a CTRLE (control-E) обозначает конец файла? LDA /OPEN1 LDX #OPEN1 JSR DOSCOM LDA /READ1 LDX #READ1 JSR DOSCOM LDA /OPEN2 LDX #OPEN2 JSR DOSCOM
Дисковая операционная система ЭВМ Эпл 335 LDA /WRITE2 LDX #WRITE2 JSR DOSCOM LOOP JSR RDKEY СМР #CTRLE BEQ DONE JSR COUT JMP LOOP DONE JSR COUT LDA /CLOSE LDX #CLOSE JSR RTS DOSCOM OPEN1 BYT $84 ASC "OPEN Fl" BYT CRET READ1 BYT $84 ASC "READ Fl" BYT CRET OPEN2 BYT $84 ASC "OPEN F2" BYT CRET WRITE2 BYT $84 ASC "WRITE F2‘ BYT CRET CLOSE BYT $84 ASC "CLOSE" BYT CRET б)' Как можно улучшить последние строки приведенной выше программы? (Подсказка: обратитесь к концу статьи 61).
336 Статья 92 Статья 92 Дальнейшие сведения о текстовых файлах Кроме рассмотренной возможности чтения и записи тексто- вых файлов вашей собственной программой, т. е. программой пользователя, имеется еще возможность считывать файлы, из- менять их определенным образом и записывать обратно на диск с помощью специальной программы, называемой редактором. Важнейшая функция редактора — вносить изменения в про- граммы. Команды программы LISA, такие, например, как I и D, рассмотренные нами в статье 46, как раз и являются частью редактора, входящего в систему LISA. Аналогично команды Бейсика LIST, RUN, SAVE и проч, являются частью редактора системы Бейсик. Эти редакторы имеют ограниченный состав команд и предназначены для работы с файлами одного опре- деленного вида. Более универсальные редакторы (например, ре- дактор PIE, который можно использовать на ЭВМ Эпл) не только оперируют с произвольными текстовыми файлами, но и содержат многие команды, отсутствующие в редакторах LISA или Бейсик. В частности, вы можете провести поиск в текстовом файле всех включений некоторой символьной строки (например, HIM) и заменить ее всюду другой символьной строкой (напри- мер, HIM OR HER) 1'>. Вы можете также выделить часть суще- ствующего файла и перенести ее на другое место в том же файле. Универсальный редактор не всегда оказывается лучше ре- дактора специального назначения, такого, как редактор LISA. В частности, редактор LISA, предназначенный для работы с про- граммами на языке ассемблера LISA, обнаруживает ошибки в этих программах. Кроме того, LISA работает быстрее редактора PIE. Редактор LISA записывает программы на диск в формате, отличном от формата, используемого другими редакторами, на- пример PIE. Однако в системе LISA предусмотрена возможность вывода на диск программ, написанных на языке ассемблера, не только в виде двоичных файлов (что .выполняется командой SAVE), но и в виде текстовых файлов, которые могут обрабаты- ваться и другими редакторами. Для этого служит команда W (от Write — записать), которую используют вместо команды SAVE. Например, вместо SAVE TEST7 можно ввести команду 1 Him — его; him or her — его или ее. — Прим. пере».
Дальнейшие сведения о текстовых файлах 337 W TEST7, которая выводит текущую программу на диск в тек- стовый файл с именем TEST7. Для чтения текстового файла в редакторе LISA используют прием, требующий специального объяснения. Как мы уже виде- ли, команда I) {control-D) системы LISA предназначена для вы- полнения стоящей вслед за ней команды ДОС Эпл. В то же время в ДОС Эпл существует команда EXEC f, которая считывает текстовой файл f и рассматривает затем каждую строку этого файла, как если бы она была введена с клавиатуры. Если вы вводите строку EXEC TEST7 в качестве команды ДОС Эпл, строки файла TEST7 считываются далее командами DOS Эпл, как.это было бы при вводе их с клавиатуры; этим и объясняется обозначение команды ЕХЕС — execute a file of commands — вы- полнить командный файл. (Это напоминает «каталогизирован- ную процедуру» в больших вычислительных системах IBM.) Однако при выполнении команды системы LISA DEXEC f каждая строка файла f рассматривается, как если бы она была введена с клавиатуры под управлением системы LISA, посколь- ку система LISA ожидает теперь «свою» следующую команду. Теперь представьте себе, что первой строкой файла является символ I (команда вставки системы LISA), а оставшаяся часть файла f представляет собой программу на языке ассемблера LISA. Тогда команда DEXEC f будет выполнена как команда вставки, за которой следуют вставляемые строки; другими сло- вами, в начале процесса редактирования эта команда выполнит чтение с диска текстового файла f. Для использования описанного приема текстовой файл нуж- но записать на диск командой W, при этом в начале файла до- бавляется строка, содержащая INS (вариант команды встав- ки I). В этом случае команда JDEXEC f считает файл с диска. Следует отметить, что команда DEXEC f действует аналогично команде АР f (uppend f — присоединить f), а не команде LOAD f (загрузить f). Если вы работали с некоторой програм- мой в системе LISA, описанный процесс приведет к тому, что строки текстового файла будут вставлены после данной про- граммы. Чтобы избежать этого, следует перед командой DEXEC f ввести команду NEW системы LISA (аналогичную команде NEW Бейсика), которая укажет системе, что начинается работа с но- вой программой. В этом, конечно, нет необходимости в начале процесса редактирования (когда обычно и вводится файл). За- метьте также, что после выполнения команды DEXEC f следует ввести команду control-E (и вслед за ней возврат каретки), ко- торой всегда заканчивается группа вставляемых строк. Листинги ассемблирования программой LISA также можно преобразовать в текстовые файлы с помощью другого приема. 22—589
338 Статья 92 Псевдокоманда ассемблера LISA DCM "с" (от dick command с — дисковая команда с) приводит на окончательной стадии ас- семблирования к выполнению с как команды ДОС. Заметьте, что это не команда, а псевдокоманда; вы включаете ее в свою программу на языке ассемблера. Пусть, например, вы хотите получить файл с листингом LIST3. Тогда первыми двумя предло- жениями вашей программы должны быть псевдокоманды DCM "OPEN LIST3" и DCM "WRITE LIST3", а перед предложением END надо вставить псевдокоманду DCM "CLOSE LIST3". По- сле выполнения команд OPEN и WRITE весь текст, в обычном случае выводимый на экран дисплея, теперь будет пересылаться в файл LIST3, где и будет образован листинг. С помощью псевдокоманды NLS (по list — прекратить выдачу листинга) можно образовать частичный листинг. Команда пре- кращает .вывод листинга программы (на экран или в файл), пока в программе не встретится.«обратная» псевдокоманда LST (list — выдавать листинг). Файл с листингом можно также пе- реслать печатающему устройству, при этом псевдокоманда PAG (new page—новая страница) позволяет начать печать этого файла на новой странице. Многие программисты, когда пишут длинную программу на языке ассемблера, используют псевдо- команду PAG в начале каждой подпрограммы1). Упражнения 1. Почему для загрузки текстового файла требуется ко- манда DEXEC /? Почему нельзя пользоваться командой LOAD f? 2. Предположим, что с целью создания двух копий лис- тинга в файлах LIST1 и LIST2, в начало программы на языке вставлены строки DCM "OPEN LIST1" DCM "WRITE LIST1" DCM "OPEN LIST2" DCM "WRITE LIST2" Из этого, однако, ничего не получится. Почему? И что произой- дет в действительности? >[< 3. Что появится на экране дисплея в процессе ассембли- рования приведенной ниже программы? LDA Р1 STA Р2 NLS 11 11 В системе LISA 2.5 есть псевдокоманда TTL (title — заголовок); TTL "s" приводит к тому, что строка s появляется в качестве заголовка в нача- ле каждой страницы листинга.
Эмуляторы 339» LDA РЗ STA Р4 LST LDA Р5 STA Р6 BRK END (Учтите, что сама команда LST не выводится на экран; коман- да NLS, наоборот, выводится.) Статья 93 Эмуляторы Представьте себе, что вы заменили устаревшую ЭВМ маши- ной Эпл. После того как вы продали или выбросили эту уста- ревшую ЭВМ, у вас остались хорошие отлаженные программы, написанные на языке ассемблера. Можно ли каким-то образом выполнять эти программы на ЭВМ Эпл? На каждой ЭВМ ис- пользуется свой собственный язык ассемблера, поэтому непо- средственное выполнение этих программ невозможно; однако' это можно сделать с помощью специальной программы, назы- ваемой эмулятором. В принципе эмулятор — это программа, позволяющая вам выполнить на машине X программу, написанную для машины Y. В нашем случае машина X — это система 6502. Предположим,, что машина Y использует п различных команд с кодами опера- ции kb k2,...,ka. Тогда эмулятор в своей основе представляет собой программу с п подпрограммами, по одной для каждого &i. Работа эмулятора начинается с загрузки в память машины X данной программы. Далее эмулятор определяет код операции первой команды программы. Пусть это код тогда эмулятор- вызывает свою j-ю подпрограмму, задача которой — выполнить, действия, обозначаемые данным конкретным кодом операции- 22»
340 Статья 93 Предположим, что машина Y содержит 32-разрядный ре- гистр Q. Предположим далее, что команда с кодом операции kj — это команда «запись Q»; она пересылает содержимое ре- гистра Q в некоторую область памяти, которую мы будем на- зывать С и которая также имеет длину 32 разряда. В этом случае в программе эмулятора должны быть зарезервированы 4 байта памяти для имитации регистра Q и по 4 байта для каж- дого 32-разрядного слова, подобного С. J-я подпрограмма долж- на переслать байт за байтом, 4 байта Q в 4 байта С. Другие подпрограммы эмулятора выполняют аналогичные функции. Машина Y, как и вообще любая ЭВМ, содержит ско- рее всего много регистров. Каждому из них должен соответство- вать эмулированный регистр — 4 байта памяти, аналогично 4 байтам для регистра Q. Оперативной памяти машины Y должна соответствовать эмулированная оперативная память, и каждому адресу а в машине Y должен соответствовать адрес эмулированной памяти в системе 6502, значение которого можно вычислить, зная значение а (эти значения совсем не обязатель- но равны). Последнее, что должна выполнить j-я подпрограмма — это настроить эмулятор на адрес следующей команды. Программа эмулятора должна эмулировать, или выполнять в режиме эму- ляции, команды программы, написанной для машины Y, коман- ду за командой, в том порядке, в котором их выполняла бы машина Y. Для этого j-я подпрограмма модифицирует содержи- мое эмулированного программного счетчика, представляющего собой еще одну группу байтов, как и для других эмулированных регистров. Пусть в нашем примере с командой «запись Q» эмулирован- ный программный счетчик содержит адрес р. Тогда j-я подпро- грамма прибавит 1 (или другое небольшое целое число к, если команда «запись Q» состоит из к слов или к байт) к (3. Если вместо команды «запись Q» выполняется команда «переход на L», тогда соответствующая подпрограмма должна загрузить в эмулированный программный счетчик адрес L. Система описан- ных приемов позволяет эмулировать также команды условных переходов. После завершения j-й подпрограммы основная про- грамма эмулятора с помощью эмулированного программного счетчика находит код операции следующей команды. Можно эмулировать и операции с индексными регистрами. Возникает вопрос: как определить адрес ячейки эмулированной оперативной памяти, к которой производится обращение? Обыч- но адрес этой ячейки входит в состав команды, на которую ука- зывает эмулированный программный счетчик, но, если выпол- няется команда с индексной адресацией, мы прибавляем к этому адресу содержимое эмулированного индексного регистра. Это
Эмуляторы 341 действие выполняется основной программой эмулятора, поэтому найденный адрес могут использовать все п подпрограмм. Легко сообразить, что эмуляция — процесс очень медленный. Типичным является замедление скорости выполнения эмулируе- мой программы в 100 раз. Поэтому эмуляцию следует приме- нять только при крайней необходимости. Эмуляция используется не только для устаревших ЭВМ, но и для новых, не имеющих еще достаточно развитого программ- ного обеспечения, или еще до их фактического приобретения. Тогда машина Y — это новая ЭВМ, а машина X — существую- щая ЭВМ, на которой работает эмулятор. В таких случаях пишется ассемблер, работающий на'машине X, который создает программу для машины У. Такая программа называется кросс- ассемблером в отличие от обычного ассемблера, который созда- ет программы для той же машины, на которой он работает. Эмулировать можно машинный язык, не соответствующий никаким реально существующим ЭВМ. Примером является SWEET16, машинный язык 16-разрядной ЭВМ, которая так и не была построена. Однако для процессора 6502 и, конкретно, для ЭВМ Эпл написан широко используемый эмулятор языка SWEET16. Некоторые ЭВМ вообще всегда эмулируют какую-то другую машину. Например, многие модели IBM 370 включают процес- соры, в действительности гораздо более простые, чем IBM 370; эта машина эмулируется с помощью аппаратного эмулятора. Такой подход называется микропрограммированием (не следует смешивать с применением микропроцессоров1), например 6502). ЭВМ с микропрограммным управлением часто эмулирует дру- гую ЭВМ с помощью аппаратного эмулятора. Упражнения 1. Предположим, что с помощью процессора 6502 эмули- руется большая ЭВМ UNIVAC 1110. Каждый адрес в системе 6502 — это адрес 8-разрядной величины, называемой байтом; точно также каждый адрес в системе 1110 — это адрес 36-раз- рядной величины, называемой словом (не путать с английским словом). Каждое эмулированное 36-разрядное слово занимает в системе 6502 5 байт (поскольку 5X8=40 разрядов достаточно для его хранения). Для эмулирования памяти ЭВМ UNIVAC 1110, содержащей х байт и начинающейся с 16-разрядного ад- реса р, используется область а памяти в системе 6502, содержа- >> В английском языке существуют созвучные термины microprogramming (микропрограммирование) и microcomputing (применение микроЭВМ или микропроцессоров), которые автор и предлагает не путать.— Прим, перев.
342 Статья 94 щая 5х байт. Для эмуляции команды L А5, N («загрузка А5 величиной N») следует переслать 5 байт с адресами от Р до Р+4, имитирующими содержимое ячейки с адресом N, в область памяти размером 5 байт, эмулирующую регистр А5. Напишите формулу для Р как функции N, а и р. 2. Для описанной выше ситуации рассмотрите эмулируемую команду L А5, Т, А1 («загрузка А5 величиной Т(&), где k — ин- декс, содержащийся в индексном регистре А1»). Напишите фор- мулу для Р как функции п, а, ₽ и k. 3. >|с а) Команды ЭВМ UNIVAC 1110, описанной в упр. 1, занимают 36-разрядное слово каждая. Предположим, что ко- манда с адресом у эмулируется в ячейках с адресами от q до <7+4, и эмулированный программный счетчик содержит q. Как должно измениться его содержимое после эмуляции команды L А5, N? б) Ответьте на тот же вопрос, считая, что в эмулированном программном счетчике не q, а у. (Заметьте, что эмулированный программный счетчик может содержать либо действительный адрес в системе 1110, либо соответствующий ему адрес в систе- ме 6502; выбор того или иного решения делается на этапе раз- работки эмулятора.) Статья 94 Интерпретаторы Интерпретатор напоминает эмулятор, но, вместо того чтобы выполнять программу, написанную на машинном языке для не- которой машины Y, интерпретатор выполняет программу, напи- санную на некотором языке L. Этим языком может быть Бейсик, Фортран, Паскаль и т. д. Работа интерпретатора во многом напоминает работу эму- лятора. В частности, в интерпретаторе имеется нечто вроде эму- лированного программного счетчика; но вместо указания адреса команды машинного языка этот счетчик указывает адрес памя- ти, где хранится текущее предложение языка L. Предположим теперь, что в языке L имеются п типов пред- ложений (например, в Бейсике могут быть предложения IF, GO ТО, FOR и др.). Тогда интерпретатор должен содержать
Интерпретаторы 343 п подпрограмм, по одной для каждого типа предложений, точно так же как в состав эмулятора входит п подпрограмм, по одной для п различных команд машинного языка. Интерпретатор хранит список всех переменных интерпрети- руемой программы. К этому списку обращаются упомянутые выше п подпрограмм. Пусть, например, текущим является пред- ложение J=K+3. Это предложение назначения; поэтому долж- на быть вызвана подпрограмма, выполняющая назначения. Эта подпрограмма прибавит 3 к величине К (в списке переменных) и запишет результат в переменную J (в том же списке). Заметь- те, что интерпретатор сам является программой, в которой могут быть переменные J и К; однако они не имеют никакого отноше- ния к переменным J и К в интерпретируемой программе. Интерпретатор хранит также список меток и соответствую- щих им адресов. Если в интерпретируемой программе имеется предложение GO ТО 100 и предложение с меткой 100 находится сейчас в ЭВМ по адресу а, тогда в списке меток будет храниться метка 100 и соответствующий ей адрес а. Когда будет интерпре- тироваться предложение GO ТО 100, подпрограмма, выполняю- щая операцию GO ТО, найдет в списке метку 100 и определит адрес а, который следует загрузить в эмулированный программ- ный счетчик. Кроме списка меток, имеется еще список ключевых слов (в Бейсике это IF, READ, DATA, DIM и проч. — вообще все слова, имеющие в языке специальное значение). В неко- торых языках ключевые слова зарезервированы — их не разре- шается использовать в качестве идентификаторов. Теперь, наконец, мы можем ответить на очень важный во- прос, который не мог быть выяснен при изучении вами Бейсика. Все микроЭВМ воспринимают программы, написанные на Бей- сике, и вам может показаться, что они являются «Бейсик-ма- шинами» в том смысле, что непосредственно «знают» Бейсик. В действительности, однако, это не так. Единственный язык, ко- торый непосредственно «знает» любая ЭВМ,— это ее собствен- ный машинный язык. Если же вы работаете на Бейсике, то фак- тически почти все время вы общаетесь с интерпретатором, как это описано выше (вы можете, конечно, работать и с компиля- тором, о чем будет идти речь в следующей статье). Интерпретаторы различаются способом хранения в памяти интерпретируемых ими программ (исходных программ). Чистый интерпретатор хранит в памяти исходную программу в точности в том виде, в каком она была введена в ЭВМ. Так, если в ис- ходной программе имеется строка J=K+3, в памяти будет со- держаться символьная строка "J=K+3". Смешанный интер- претатор (semi-interpreter) транслирует исходную программу в некоторый промежуточный язык, и именно в этой форме про- грамма затем интерпретируется. Промежуточный язык для ин-
344 Статья 94 терпретатора — это все равно, что язык SWEET16 для эмулято- ра: машина не может выполнить программу на языке непосред- ственно, но с помощью интерпретатора эта программа выпол- няется легко. В промежуточном языке имена переменных очень часто за- меняются их индексами в таблице. Чистому интерпретатору, чтобы интерпретировать предложение J=K4-3, обязательно тре- буется найти в таблицах J и К, на что требуется значительное время. Если, однако, J — это пятая переменная в программе, а К — шестая, тогда в промежуточном языке обозначения J и К будут заменены на индексы 5 и 6 соответственно. Смешанному интерпретатору гораздо легче интерпретировать такое предло- жение, потому что не нужно искать в таблице (константа 3 так- же будет заменена некоторым индексом в таблице). Другой характерной чертой смешанных интерпретаторов яв- ляется преобразование всех ключевых слов, испЪльзованных в исходной программе, в индексы списка ключевых слов. Такое преобразование также ускоряет работу интерпретатора; чистому интерпретатору нужно искать в таблице все ключевые слова каждый раз, когда интерпретируется данное предложение. Это приводит к особенно большим потерям времени, если предло- жение находится в цикле, и выполняется, и, соответственно, ин- терпретируется многократно. Интерпретаторы, как и эмуляторы, работают медленно. В микроЭВМ это, однако, часто не имеет значения; как уже от- мечалось, машинное время на микроЭВМ обычно столь дешево, что может считаться бесплатным. Упражнения 1. Предположим, что GO ТО является в данном языке един- ственным ключевым словом, начинающимся с буквы G. Может ли чистый интерпретатор для такого языка определить по одной этой букве, является ли данное предложение предложением GO ТО? Объясните. 2. Предположим, что предложение некоторого языка на- чинается символами А=ВНсС (возможно, дальше идут другие символы). Пусть известно, что А, В и С — простые (не индекси- рованные) переменные. Должен ли чистый интерпретатор для этого языка, прочитав первые пять символов предложения, сразу же найти в своих таблицах значения В и С и перемножить их? Объясните. 3. Когда смешанный интерпретатор интерпретирует предло- жение J = K+3, то, как было отмечено в тексте статьи, не возни- кает необходимости в просмотре таблиц; однако в процессе трансляции предложения J=K+3 с исходного на промежуточ-
Ассемблеры и компиляторы 345 ный язык величины J и К должны быть найдены в таблицах переменных. Отсюда можно сделать вывод, что чистый интер- претатор работает эффективнее смешанного, поскольку оба интерпретатора на каком-то этапе должны проводить поиск в таблицах, но, кроме этого, смешанному интерпретатору требу- ется еще какое-то время для трансляции на промежуточный язык. Однако, это не обязательно так. Объясните, почему. Статья 95 Ассемблеры и компиляторы Может быть, теперь вам, наконец, захотелось узнать, как же пишутся такие программы, как LISA. Ассемблер преобразует программу Р, написанную на языке ассемблера, в программу Q на машинном языке. Вы можете считать, что на вход ассемблера поступает программа Р, а на выходе получается программа Q. Большинство ассемблеров дей- ствительно выводят программу Q, но LISA оставляет Q в памя- ти для последующего использования; с помощью команды SAVE эту программу можно при необходимости вывести на диск. Программа Р называется исходной программой; программа Q.— объектной. Основная задача ассемблера заключается в преобразовании каждого предложения языка ассемблера в его эквивалент на машинном языке. Этот процесс состоит из двух этапов. Первый этап, заключающийся в нахождении кодов операций, весьма прост. В программе ассемблера имеется таблица всех мнемоник и соответствующих им кодов операций. Когда в программе Р встречается обозначение TYA, ассемблер отыскивает это обо- значение в таблице, берет из таблицы соответствующий код операции (шестнадцатеричное 98) и заносит его в программу Q. Некоторые мнемоники процессора 6502 имеют более одного кода операции. Встретившись с такой мнемоникой, ассемблер должен проанализировать оставшуюся часть данного предложе- ния языка ассемблера. Так, если в предложении написано STA Т—!2,Y, ассемблер LISA фиксирует наличие запятой и сим- вола Y, отмечает, что выражение Т—12 не заключено в скобки
346 Статья 95 (при наличии скобок код операции был бы 91), и определяет, что код операции равен 99. Второй этап трансляции заключается в вычислении адресов. Если, например, адрес Т составляет 08С0, то адрес, входящий в команду STA Т—!2,Y, равен 08ВЕ (или с переставленными бай- тами BE 08). Этот этап сложнее предыдущего, потому что в момент трансляции команды STA Т—!2,Y адрес Т может быть еще неизвестен, если, например, определение Т DFS1100 стоит в исходной программе Р где-то позже и еще не прочитано. Для решения этой проблемы большинство ассемблеров рабо- тает в несколько (по крайней мере 2) проходов. При первом проходе ассемблер читает входную программу Р, но еще не об- разует программу Q. Вместо этого он создает в памяти таблицу обозначений. Это таблица всех идентификаторов (таких, как Т) вместе с их адресами. Во втором проходе ассемблер еще раз читает входную программу Р, и на этот раз образует программу Q, пользуясь информацией, содержащейся в таблице обозна- чений. В программе ассемблера, как и в эмуляторе, имеется эмули- рованный программный счетчик, только используется он по- другому. Когда ассемблер встречается с предложением ORG z, в эмулированный программный счетчик С загружается значение z. Каждый раз при чтении команды или объявлении данных, которые в программе Q должны занять k байт, в счетчик С до- бавляется k. Каждый раз, когда ассемблер встречается с по- меченной командой или помеченным объявлением данных, дан- ная метка записывается в таблицу обозначений (в первом про- ходе) вместе с текущим значением счетчика С, которое и пред- ставляет собой адрес, соответствующий этой метке. Кроме определения кодов операций и адресов, ассемблер выполняет еще различные действия, связанные с транслируемы- ми командами. Применительно к процессору 6502 к ним отно- сится вычисление относительных адресов для команд условных переходов, а также преобразование в шестнадцатеричную фор- му данных, входящих в команды с непосредственной адресацией. Если, например, в программе Р содержится команда LDA #!100, то перед тем, как поместить операнд 100 в программу Q, надо найти его шестнадцатеричный эквивалент 64. Ассемблеры должны также обрабатывать псевдокоманды в исходной программе Р. В этом случае действия ассемблера не- много напоминают действия эмулятора. Если язык ассемблера включает п различных псевдокоманд, то в программе ассембле- ра имеется п подпрограмм, по одной для каждой псевдокоманды. Мы уже видели, что подпрограмма, обрабатывающая предло- жение ORG k, загружает значение k в эмулированный программ- ный счетчик.
Ассемблеры и компиляторы 347 Компилятор похож на ассемблер, но если исходная про- грамма для ассемблера пишется на языке ассемблера, то ис- ходная программа для компилятора, например, с языка Бейсик пишется на Бейсике. В обоих случаях объектные программы по- лучаются на машинном языке. Компилятор, однако, чаще всего сравнивают с интерпретатором, поскольку эти две программы-, будучи различными по принципу действия, предназначены для выполнения разными Способами одного и того же, именно, вы- полнения программы, написанной на каком-либо языке, напри- мер Бейсике. Компилятор представляет собой .весьма длинную и сложную программу, и ее выполнение требует значительного времени — иногда большего, чем потребовала бы интерпретируемая про- грамма. В таком случае, очевидно, лучше интерпретировать, чем компилировать. К тому же многие микроЭВМ имеют слишком скромные аппаратные ресурсы, чтобы поддерживать компиля- тор: он просто не поместится в оперативную память. С другой стороны, объектная программа после компиляции выполняется всегда значительно быстрее, чем соответствующая ей исходная программа в режиме интерпретации. Поэтому, если некоторую программу предполагается выполнять многократно, лучше ис- пользовать компилятор (если он имеется), чем интерпретатор. Программы, написанные на Фортране, почти всегда компи- лируются, а не транслируются. Еще до создания микроЭВМ именно Фортран, а не Бейсик, был наиболее употребительным алгебраическим языком, а на больших ЭВМ, таких, как IBM 4300, он и сейчас используется чаще Бейсика. Дизассемблер представляет собой программу, которая со- вершает обратное преобразование программы на машинном языке в эквивалентную ей программу на языке ассемблера. Диз- ассемблер удобно использовать для анализа чужих объект- ных программ, хотя по ряду причин это обратное преобразова- ние оказывается не вполне удовлетворительным (например, из-за произвольного выбора имен переменных). Упражнения 1. Представьте себе таблицу мнемоник в ассемблере. Существуют ли какие-нибудь причины, по которым эта табли- ца должна быть упорядоченным массивом? Объясните. 2. Представьте себе таблицу символических обозначений в ассемблере. Существуют ли какие-нибудь причины, по кото- рым эта таблица должна быть упорядоченным массивом? Что здесь отличается от предыдущего упражнения? Объясните. (Подсказка: см. статью 86.) >|с 3. Представьте, что у вас есть программа, которая пре- образует программы на Бейсике в соответствующие им про-
348 Статья 96 граммы на языке ассемблера. Можно ли воспользоваться ею для выполнения программ на Бейсике в качестве альтернативы использованию интерпретатора? Объясните. Статья 96 Структурное программирование Как научиться писать более эффективные программы? Этот общий вопрос с давних пор занимает умы специалистов по вычислительной технике. Сюда относится как методика напи- сания более эффективных программ, так и повышение эффек- тивности процесса написания программ. Иногда эти цели вхо- дят в противоречие друг с другом; как было показано в статье 37, приходится искать компромисс между трудовыми затратами программиста и такими характеристиками программы, как за- нимаемая ею память и время выполнения. Программа, отли- чающаяся высокой эффективностью, может потребовать для написания неприемлемого времени. Один из способов более эффективного написания про- грамм— повышение собственной организованности. Мы все стал- кивались с «организованными» и «неорганизованными» людь- ми; при этом часто человек организован в одной области и неорганизован в другой. Многие люди программируют не так эффективно, как могли бы, лишь потому, что выполняют эту работу слишком неорганизованно. Организации, или структу- рированию; процесса программирования посвящено значитель- ное количество исследовательских работ, и было высказано много подчас противоречивых мнений, каким должно быть структурное (или «организованное») программирование. Уже на самых ранних стадиях изучения структурного про- граммирования было подмечено, что слишком многие про- граммисты используют в любом языке программирования толь- ко простые предложения. Сюда относятся назначения, предло- жения IF в простейшей форме (IF условие GO ТО метка) и ввод-вывод. В то же время программисты не пользуются под- программами, итерациями • (например, предложение FOR в Бейсике) и более общими формами предложения IF, имеющи- мися .в ряде языков (как, например, IF условие THEN предло-
Структурное программирование 349 Предложения структурного программирования Неформальное описание Соответствующая по- следовательность назна- чений и условных переходов IF С THEN SI ELSE S2 WHILE C DO S REPEAT SEQ UNTIL C Если условие С истинно, вы- полнить предложение S1; в противном случае выполнить предложение S2 Повторять предложение S, по- ка условие С остается истин- ным (0 или более раз) Повторять последовательность предложений SEQ (1 или бо- лее раз) до тех пор, пока усло- вие С не станет истинным IF С THEN GO ТО m; S2; GO TO n; m: SI; n; GO TO n; m: S; n: IF C THEN GO TO m; m: SEQ; IF NOT C THEN GO TO tn; CASE К OF Выполнить IF K<>1 THEN GO TO SI; GO TO n; 1: SI; предложе- n2: IF K<>2 THEN GO TO n3; S2; GO TO n; 2: S2; ние Si n3: IF K<>3 THEN GO TO n4; S3; GO TO n-. 3: S3; (только), ec- n4: ли значение nm: IF KOm THEN GO TO error; Sm; tn: Sm К есть t, n: причем i равно либо 1, либо 2, либо m Рис. 96.1. Предложения структурного программирования. жение-1 ELSE предложение-2). Все эти более сложные пред- ложения можно смоделировать, или «сфабриковать», с помощью более простых, но в результате обычно получается неоправ- данно запутанная программа. Голландский специалист по вычислительной технике Э. Дейкстра выразил это таким образом: «Уже давно было замечено, что квалификация программистов является убываю- щей функцией от плотности предложений GO ТО в создавае- мых ими программах»1). Причина этого заключается в том, что более сложные конструкции языка, чаще используемые опыт- ными программистами, всегда могут быть представлены с по- мощью конструкций, содержащих предложения GO ТО, как это показано на рис. 96.1. Отсюда сразу следует, что програм- мы плохих программистов будут насыщены предложениями 1 Communications of ACM, 11, N 3 (March 1968) 147—148.
550 Статья 96 GO ТО. Четыре предложения структурного программирования на рис. 96.1 в той или иной форме используются во многих язы- ках программирования (на рис. 96.1 приведены примеры из языка Паскаль1’). В языке ассемблера таких предложений ко- нечно нет, но мы всегда можем сначала написать программу по правилам структурного программирования на языке высо- кого уровня, если это облегчит нам понимание программы, а затем вручную перевести ее на язык ассемблера. Дейкстра продолжает: «Я пришел к убеждению, что пред- ложение GO ТО должно быть устранено из всех языков про- граммирования «высокого уровня» (т. е. отовсюду, за исклю- чением, возможно, простых машинных кодов)». Многие про- граммисты пытались следовать этому совету Дейкстры, и се- годня все согласны с тем, что он не приводит к желаемому результату; в ряде случаев (в основном при анализе ошибок) предложения GO ТО оказываются проще для понимания, чем соответствующие им «структурированные» формы. Другой метод повышения вашей организованности и улуч- шения качества программирования заключается в нисходящем проектировании, которое напоминает процесс составления пла- на письменной экзаменационной работы. Вы выписываете на листке бумаги все, что должна делать ваша программа, а за- тем расширяете каждую запись в подпрограмму или программ- ную секцию. При желании вы можете так написать основную программу, что она будет выглядеть в точности, как план; в ней практически не будет ничего, кроме предложений вызова подпрограмм — по одному предложению на каждую строку плана, да еще очень ограниченное число итераций и предложе- ний анализа ошибок. Многие программисты считают, что им гораздо легче понимать собственные программы, если они на- писаны таким способом. Еще один прием, повышающий вашу организованность и эф- фективность программирования, — ручная прогонка, описанная в статье 44. Ручная прогонка особенно эффективна, когда она проводится коллективно группой программистов, стремящихся найти ошибки в проверяемой программе. Наконец, есть еще и организационный прием: назначение лучшего программиста в данной группе главным программи- стом. Это, впрочем, легче сказать, чем сделать, потому что очень часто лучший программист не является прирожденным лидером, в то время как другие программисты в той же группе обладают этим качеством. Важно постоянно поддерживать ав- торитет главного программиста, чтобы другие программисты *> В Паскале за меткой должно следовать двоеточие, а за предложением (обычно) — точка с запятой; таким образом mt S; обозначает предложение S с меткой т.
Структурное программирование 3511 в группе с уважением относились к его полномочиям и не пы- тались вносить в проект программы; не' санкционированные главным программистом изменения. Упражнения 1. Для каждого из приведенных ниже примеров напишите ' соответствующие последовательности назначений и условные переходов по аналогии с правым столбцом рис. 96.1. * a) IF С THEN REPEAT QI UNTIL D ELSE Si 6) WHILE Cl DO IF C2 THEN SI ELSE S2 2. Для каждого из приведенных ниже примеров напишите соответствующие структурированные программы без использо- вания предложений GO ТО (примеры таких программ приве- дены в предыдущем упражнении) *): * a) IF Cl THEN GO TO m2; GO TO n; tn: S2; n: IF C2 THEN GO TO m; GO TO n2; m2: SI; n2: ' 6) m: IF Cl THEN GO TO m2; S2; GO TO n; m2: SI; n: IF NOT C2 THEN GO TO m. 3. Для каждого из приведенных ниже предложений струк- турного программирования напишите соответствующую после- довательность команд языка ассемблера: * a) IF Cl THEN SI ELSE WHILE C2 DO S2 6) REPEAT IF Cl THEN SI ELSE S2 UNTIL C2 Обратите внимание на важное отличие языка Паскаль от многих дру- гих языков, связанное с обозначением меток. В п. а) т: представляет собой метку, a IF, хотя и начинается с позиции 1, не метка. Так нельзя написать в программе языка ассемблера, а вот в Паскале можно. Система Паскаль может определить, что IF — не метка; среди прочих отличий от меток, после IF не стоит двоеточие.
252 Статья 97 Статья 97 Двоичные и шестнадцатеричные дроби До сих пор все числа, которые мы использовали в вычисле- ниях, -были целыми. Как представить в двоичной или шестнад- цатеричной системе дробные числа? Один из способов заклю- .0 0 .0 .0001 1/16 .1 .001 1/8 .2 .ООП 3/16 .3 .01 1/4 .4 .0101 5/16 .5 .011 3/8 .6 .0111 7/16 .7 .1 1/2 .8 .1001 9/16 .9 .101 5/8 .А .1011 11/16 .В .11 3/4 .С .1101 13/16 .D .111 7/8 ,Е .1111 15/16 .F 1.0 1 1.0 Двоичные Простые Шестнадцатеричное Рис. 97.1. Двоичные и шестнадцатеричные дроби. чается .в использовании принципа образования десятичных дробей. Как мы вычисляем значение десятичной дроби .738? Оно, несомненно, равно 738/1000, но почему деление произ- водится на 1000, а не на 100 или 10000? Потому что в числе 738 три цифры, а 10 в третьей степени есть 1000. Мы можем применить ту же идею к двоичным числам. Так, .101 пред- ставляет 5 (т. е. 101 в двоичной системе), деленное на 8 (пред- ставляет собой 2 в третьей степени, поскольку в числе 101 три цифры); другими словами, .101 равно 5/8. Число .101 называ-
Двоичные и шестнадцатеричные дроби 353 ется двоичной дробью (по аналогии с десятичными дробями); первые 16 дробей вместе с нулем показаны на рис. 97.1. Точка в обозначении .101 носит название двоичной точки (по аналогии с десятичной точкой). Нам понятно, что некоторые десятичные дроби являются конечными, а другие — нет. Так, 3/4 равно 0.75, потому что- 3/4=75/100; но 1/3 представляет собой бесконечную десятич- ную дробь 0.333333... ( и т. д. до бесконечности). То же отно- сится и к двоичным дробям; например, двоичная дробь 0.01010101... (и т. д. до бесконечности) представляет собой 1/3. Данную десятичную бесконечную дробь, например .736736736... (и т. д. до бесконечности), можно преобразовать в простую дробь делением повторяющейся части (в данном случае 736) на 10d—1, где d — количество цифр в повторяю- щейся части. В данном случае мы получаем 736/999 (вспомним, что просто .736 представляет собой 736/1000). Аналогичные правила применимы и к двоичным дробям; так, .01 равно 1/4 (т. е. 1, деленная на вторую степень двух, поскольку в .01 две цифры), а .01010101... (и т. д. до бесконечности) равно 1/3, по- скольку 3=22 — 1. (Почему мы не можем представить 1/3 в ЭВМ в виде двух целых 1 и 3, а затем складывать, вычитать, умножать и делить дроби в соответствии с правилами, которые мы изучали в на- чальной школе? Потому что нам пришлось бы постоянно со- кращать получающиеся дроби, а эта операция требует очень много машинного времени. Кроме того, многие числа, с кото- рыми нам надо работать, например п, не выражаются точно через простые дроби.) Некоторые дроби, являющиеся конечными в десятичной системе, оказываются бесконечными в двоичной. Например, 3/16 представляет собой .ООП в двоичной системе, и согласно приведенному выше правилу 3/(16—1), или 3/15, равно .001100110011... (и т. д. до бесконечности), но 3/15 после сокра- щения дает 1/5, чему в десятичной системе соответствует конеч- ная дробь 0.2 (поскольку 1/5=2/10). В дополнение к десятичным дробям вроде 0.75 в десятичной системе имеются и смешанные числа, например 3.25, для которых характерно наличие и целой, и дробной частей. То же справед- ливо и по отношению к двоичной системе, например 11.01 пред- ставляет собой двоичное представление 3.25. Пусть теперь мы хотим представить 11.01 в виде 16-разряд- ной величины. Мы можем договориться, что первые 10 разря- дов нашей 16-разрядной величины отводятся для целых чисел; за ними стоит двоичная точка, а за ней — 6 разрядов для дроб- ной части. Тогда число 11.01 представится в виде 0000000011.010000. 23-589
354 Статья 97 При использовании такого представления всегда возникает вопрос, где расположить двоичную точку. Если двоичная точка стоит после первых 10 разрядов, как в приведенном примере, то мы можем выразить числа от 0 (т. е. 0000000000.000000) до 1023 63/64 (т. е. 1111111111.111111). Если этот диапазон слиш- ком велик, мы можем поставить десятичную точку в другом ме- сте, скажем, после первых 7 разрядов. Это дает нам диапазон представляемых чисел от 0 до (почти) 128, и увеличит количе- ство возможных дробей между соседними целыми числами. Таким образом получается, что мы можем расположить дво- ичную точку произвольным образом в любом месте, но опреде- лив положение точки, мы должны его зафиксировать, чтобы можно было выполнять сложение и вычитание. В этом случае действия вида 0110010000 110010 +0011010110. КЮ1 и 1001100111.011001 0100111011 011100 -0001001101.101100 0011101101.110000 выполняются как с 16-разрядными целыми числами. Это, одна- ко, возможно, только если двоичные точки «выравнены»; в про- тивном случае нам пришлось бы сдвигать одно или другое чис- ло для выравнивания двоичных точек. Описываемое представ- ление чисел носит название представления с фиксированной точкой. В представлении с фиксированной точкой отрицательные чис- ла записываются, как обычно, в виде их дополнений до двух. В этом случае диапазон чисел в нашем примере (с дво- ичной точкой после 10 цифр) становится от —512 (т. е. 1000000000.000000) до 511 63/64 (т. е. 0111111111.111111). Существуют также шестнадцатеричные дроби, содержащие шестнадцатеричные цифры (причем .8=1/2, ,С=3/4, и т. д., см. рис. 97.1). Шестнадцатеричные дроби можно преобразовать в двоичные, заменив каждую шестнадцатеричную цифру четырь- мя двоичными, как это мы делали в статье 4. Преобразование двоичных дробей в шестнадцатеричные также выполняется по правилам преобразования целых чисел. Упражнения 1. Какая дробь а/b (для десятичных а и Ь) соответствует приведенным ниже бесконечным двоичным дробям? (Сократите полученные дроби.) а) .101010... * б) .1001101... в) .100100...
Действительные числа и представление с плавающей точкой 355 2. Дайте наилучшее приближенное представление приведен- ных ниже дробей числом с фиксированной точкой без знака в форме bb.bbbbbb (т. е. состоящего из 8 бит с двоичной точкой между битами 5 и 6, считая правый бит нулевым): а) 4/5; б) 2 6/7; >|i в) л. 3. Десятичные дроби вида k/lOa являются конечными; суще- ствуют, однако, десятичные дроби (например, 1/5), которые, бу- дучи конечными, не имеют вид &/10п (во всяком случае, после сокращения). Существуют ли конечные двоичные дроби, кото- рые не имеют вид k/2n после сокращения? Почему существуют или почему не существуют? Статья 98 Действительные числа и представление с плавающей точкой Представление с фиксированной точкой, описанное в преды- дущей статье, не позволяет работать с очень большими и очень маленькими числами (такими, как 6.061 ХЮ23 или 6.626 X10-27), встречающимися в вычислениях. Для того чтобы на ЭВМ мож- но было обрабатывать действительные числа, обычно использу- ется другое представление, называемое представлением с пла- вающей точкой. В основе представления чисел с плавающей точкой лежит то, что в общем случае для определения действительного числа (например, 6.626Х 10~27) требуется следующая информация: а) знак (положительный или отрицательный); б) порядок (т. е. показатель степени десяти, в данном слу- чае — 27, хотя в формате числа с плавающей точкой в действи- тельности используется степень двух или иногда 16, а не 10); в) мантисса (т. е. число 6.626, выраженное в виде правиль- ной дроби, для чего число 6.626Х Ю-27 мы должны записать в 23»
356 Статья 98 виде .6626Х10-26. Полученная мантисса .6626 представляет со- бой правильную дробь). Указанной информации достаточно, чтобы выразить действи- тельное число, поэтому все, что нам осталось сделать — это задать формат числа. Существуют различные форматы чисел с плавающей точкой; ниже описан «короткий формат», исполь- зуемый на больших ЭВМ фирмы IBM, например, серии 4300: 1. Каждое действительное число занимает 4 байта. 2. Левый разряд первого байта является знаковым; 0 озна- чает плюс, al — минус, как и у целых чисел. 3. Оставшаяся часть первого байта является порядком. 4. Остальные 3 байта представляют собой мантиссу. 5. Порядок представляет собой степень 16 (это необычно; в большинстве форматов чисел с плавающей точкой это степень 2). 6. Мантисса рассматривается как 6-разрядная шестнадцате- ричная дробь в пределах от .100000 до .FFFFFF (если мантисса меньше .100000, мы используем соотношение .0хххххХ16п= =.хххххОХ16п-1, если нужно — многократно, чтобы нормализо- вать мантиссу, т. е. привести ее к указанному диапазону). 7. Порядок, находящийся в пределах от —64 до 63, смещают, прибавляя к нему 64; результирующая величина лежит в пре- делах от 0 до 127. Таким образом, порядок е в действительно- сти представлен числом е+64. (Это напоминает смещение на- пряжения в электронных цепях путем прибавления к нему постоянного положительного напряжения, так что при любых флуктуациях напряжение остается положительным.) Если два числа с плавающей точкой нормализованы (т. е. имеют нормализованные мантиссы, как это описано выше в п. 6), то в результате смещения число, у которого больше по- рядок, и само больше другого. Поэтому, сравнивая числа с пла- вающей точкой, мы можем сначала сравнивать их порядки, а затем, если порядки равны, сравнивать мантиссы. При сравне- нии порядков смещение преобразует знаковое сравнение с без- знаковым, как это уже рассматривалось в статье 54. Фактиче- ски и само смещение формируется так же: оно содержит 1 с цепочкой нулей. На рис. 98.1 показано представление чисел 2, 48, — 228 и 1/512 в описанном выше формате с плавающей точкой. Обра- тите внимание, как значение порядка изменяет фактическое по- ложение двоичной (или шестнадцатеричной) точки, которая «плавает» по мантиссе (в том числе «доплывая» иногда до са- мого левого или самого правого края), что и дало название это- му формату. Число нуль в формате с плавающей точкой характеризуется мантиссой, равной 0; знак и порядок в этом случае не имеют
Действительные числа и представление с плавающей точкой 357 2. О О О О О Лйитг Смещение 0 1000001 0010 I0000 I0000 I 0000 I 0000 I 0000 Порядок Мантисса f- .200000 е=7 (смещенное} (шестнадцатери чное} Число - 16е* f =161* .Z = Z 3 0.0 О 0 0 Знак Смещение 0 1000010 ООН 1 0000 1 0000 1 0000 1 0000 1 0000 Порядок Мантисса 300000 e=Z (смещенное} (шестнадцатеричное) Число = 16е*6 = 16%* .3=30(шестнадцатерично^ =* » 48 (десятичное} -10 О О О ООО Знак Смещение 1 1001000 0001 10000 1 0000 1 0000 1 0000 1 0000 Порядок Мантисса f - . 100000 е=8 (смещ енное} (шестнадцатери чное} Число=~16е* f=-168* .1=-10000000 (шестнадцатеричное) .0 0 8 0 0 0 0 0 Знак Смещение, 0 0111110 1000 10000 1 0000 1 0000 10000 1 0000 Порядок Мантисса Р= .800000 e--Z (смещенное} (шестнадцатеричное) Число= 16е к f= 16~гх.8 =. 008 (шестнадцатеричное} = = 1/512 (десятичное} Рис. 98.1, Представление действительных чисел в формате с плавающей точкой.
358 Статья 99 значения, но принято и их делать равными 0. Заметьте, что для изменения знака числа с плавающей точкой в указанном формате достаточно изменить один бит (в знаковом разряде); для чисел, записанных в форме дополнения до двух, это было не так (однако существуют другие распространенные форматы чисел с плавающей точкой, для которых это замечание неспра- ведливо) . Упражнения 1. -Запишите приведенные ниже действительные числа в ви- де нормализованных чисел с плавающей точкой в формате, со- ответствующем рис. 98.1 (используйте шестнадцатеричное пред- ставление и выразите каждую величину с помощью 8 цифр): * а) 3/8; б) -11 1/2; * в) 2113. 2. Ниже даны (в шестнадцатеричной форме) внутренние представления некоторых действительных чисел в формате с плавающей точкой согласно рис. 98.1. Найдите эти числа. a) BF140000 (выразите в виде сокращенной, насколько возможно, дроби); * б) 42650000; в) 1F000400 (выразите, как степень 2). 3. Одно из чисел предыдущего упражнения не нормализо- вано. Какое именно? Выразите это число в нормализованной форме. Статья 99 Операции с плавающей точкой Пусть мы получили несколько чисел, представленных в фор- мате с плавающей точкой. Что нам с ними делать? Работая с процессором 6502, мы вызываем подпрограммы, чтобы склады- вать, вычитать, умножать или делить эти числа, преобразовы-
Операции с плавающей точкой 359 вать их в целые и наоборот, вводить и выводить. Эти подпро- граммы напоминают подпрограммы умножения и деления це- лых чисел, уже рассмотренные нами в статьях 39 и 40. Следует заметить, что в больших ЭВМ, таких, как IBM се- рии 4300, имеются специальные команды, выполняющие сложе- ние, вычитание, умножение и деление чисел с плавающей точ- кой. Это, однако, не такое уж большое преимущество, как мо- жет показаться. В программах для процессора 6502 вместо од- ной команды умножения чисел с плавающей точкой вы исполь- зуете также одну команду вызова подпрограммы умножения чи- сел с плавающей точкой (JSR). С другой стороны, для того что- бы иметь набор команд с плавающей точкой, вы платите очень дорогую цену, поскольку ЭВМ, имеющие такие команды, во много раз дороже микроЭВМ на базе процессора 6502. Умножение и деление чисел с плавающей точкой в действи- тельности проще сложения и вычитания. Число с плавающей точкой, характеризуемое знаком s, порядком е и мантиссой f, может быть представлено в виде sXbeXf, где Ь — база (для формата чисел с плавающей точкой, описанного в предыдущей статье, &=16), a s — знак, который принимается равным 1 для положительных чисел и — 1 для отрицательных. Произведение S\XbeiXf\ на s2Xbe*Xf2 равно ($iX$2)X(&eie Хбе2)Х(ЛХ/г), причем bei Xbe* можно выразить как &б1+е2 . Отсюда следует, что для умножения двух чисел с плавающей точкой достаточно перемножить знаки, сложить порядки и перемножить мантиссы. Результат может оказаться не нормализованным — например, мантисса .3, умноженная на самоё себя, дает .09. Однако, если результат не нормализован, процедуру нормализации, описанную в предыдущей статье, следует применить только один раз. Аналогично деление s{XbeiXf\ на s2Xbe*Xf2 дает (si/s2) X X(bei/b ^2) х (fi/f2), причем be*!be* можно выразить как 2. Таким образом, в данном случае мы должны разделить знаки, найти разность порядков и разделить мантиссы. Как и в случае умножения, результат может оказаться не нормализо- ванным— мантисса .F, разделенная на .1, дает в шестнадцате- ричной системе F, но, как и раньше, процедуру нормализации следует применить только один раз (в противоположном, по от- ношению к операции умножения, направлении). Относительная сложность сложения и вычитания связана с необходимостью сдвигов, если двоичные точки в числах не вы- ровнены. Вспомним, что 235.9 _ 235.9 + 2.359 “ + 2.359 238.259
360 Статья 99 и вообще при сложении двух десятичных дробей мы должны сдвигать одну из них до тех пор, пока не выровняются их деся- тичные точки. Из рис. 98.1 видно, что двоичная точка в числе с плавающей точкой «переплывает» на одну позицию вправо каж- дый раз, когда порядок увеличивается на 1. Поэтому, если раз- ность между порядками составляет z, перед сложением меньшее число надо сдвинуть на z позиций вправо — в данном случае на z шестнадцатеричных разрядов, или 4z двоичных. То же спра- ведливо в случае операции вычитания. Знаки чисел с плавающей точкой, участвующих в операци- ях сложения или вычитания, определяют действительный тип выполняемой операции. Если знаки неравны, тогда то, что было определено как сложение, становится вычитанием и наоборот. Вычитание — это единственная операция с плавающей точ- кой, когда может потребоваться многократное применение про- цедуры нормализации. Например, шестнадцатеричное вычита- ние .532684 - .532271 .000413 дает дробь с тремя лидирующими нулями, так что процедуру нормализации следует применить три раза. Никогда не забывайте, что числа с плавающей точкой лишь приблизительно описывают действительные числа, и последова- тельность операций с плавающей точкой может легко умень- шить точность. Это в особенности касается вычитания, хотя на первый взгляд вычитание может показаться точной операцией (см. приведенный выше пример). Если первая мантисса в этом примере изменяется на единицу младшего разряда, результат становится равным .000412, или .412000Х16-3, т. е. изменяется на 1000 (шестнадцатеричное), а совсем не на 1. Сложение, ум- ножение и деление чисел с плавающей точкой могут давать ошибочный результат, даже если сами складываемые, умножае- мые или делимые числа вполне точны. В статистических расчетах также часто используются слу- чайные числа. Для хранения 16-разрядного случайного числа с переставленными байтами в ЭВМ Эпл предусмотрены ячейки RNDL и RNDH (random, low — случайное, младшее полуслово и random, high — случайное, старшее полуслово при определениях RNDL EPZ $4Е и RNDH EPZ $4F). Каждый раз, когда вызы- вается подпрограмма RDKEY, в эти ячейки начинает снова и снова добавляться 1 (перенос игнорируется), в результате чего они уподобляются вращающемуся колесу рулетки. Нажатие клавиши прекращает этот процесс, колесо рулетки останавлива- ется, и в ячейках оказывается случайное число.
Вычисления с преобразованием типов 361 Упражнения 1. Объясните своими словами, как выполняется сложение чисел с плавающей точкой 42FF0000 и 41100000 и как образу- ется результат. Проверьте свой ответ, преобразовав обе эти ве- личины в действительные числа, сложив их и преобразовав ре» зультат обратно в нормализованную форму с плавающей точ- кой. 2. Объясните своими словами, как выполняется умноже- ние чисел с плавающей точкой 412000000 и 41300000 и как обра- зуется результат. Проверьте свой ответ, преобразовав обе эти величины в действительные числа, перемножив их и преобразо- вав результат обратно в нормализованную форму с плавающей точкой. 3. Пусть Q — действительное число, имеющее форму с пла- вающей точкой 50800000 (шестнадцатеричное). Что происходит в ЭВМ при выполнении операции (Q+l)— Q? Какой момент,, описанный в тексте, иллюстрирует этот пример? Статья 100 Вычисления с преобразованием типов Теперь, наконец, мы можем приподнять завесу таинственно- сти, оставшуюся после прочтения статьи 7. В этой статье было отмечено, что все данные в ЭВМ — целые со знаком и без зна- ка, символьные коды или вообще что угодно — закодированы в виде двоичных чисел и занимают 1 или более байтов. Отсюда следует, что не разобравшись в самой программе, мы не можем определить, что представляют собой эти данные: целые числа, действительные, либо что-то еще. Это справедливо для любой ЭВМ. Известно, однако, что существуют языки программирования, в которых действительные и целые числа могут совместно уча- ствовать в вычислениях. Это, казалось бы, противоречит сказан- ному. Предположим, что мы используем интерпретатор, как это было описано в статье 94. Теперь представьте себе операцию А+В, где А и В могут быть как целыми, так и действительными
362 Статья 100 числами. Если они действительные, интерпретатор должен вы- зывать подпрограмму сложения чисел с плавающей точкой. Ес- ли они целые, интерпретатор должен сложить их по правилам сложения целых чисел. Как же интерпретатор может выбрать программу действий, если нет способа отличить действительное число от целого? Прежде всего мы должны заметить, что эта проблема воз- никает лишь в том случае, когда переменные А и В в процессе одного прогона программы выражают то целые числа, то дей- ствительные. В некоторых языках, например в Фортране, любая действительная переменная в программе остается действитель- ной на протяжении всей программы. То же справедливо и в • отношении целых, поэтому выбор, о котором шла речь, сделать всегда можно. (В некоторых версиях Бейсика все целые числа хранятся в ЭВМ как действительные. Это замедляет работу сис- темы Бейсик, потому что теперь все операции сложения должны выполняться с плавающей точкой; с другой стороны, это дает возможность системе Бейсик делать упомянутый выбор.) Общее решение поставленной проблемы заключается в ис- пользовании преобразования типов. При вычислениях с преоб- разованием типов каждая переменная в программе получает код типа, который хранится в памяти вместе со значением пере- менной. Коды типа для переменной V могут принимать различ- ные значения; в простейшем случае: TCV=0 обозначает, что V в настоящий момент — целое; TCV=1 обозначает, что V в настоящий момент — действительное. ,И значение V, и значение TCV могут изменяться по ходу выпол- нения программы. Если, например, V было целым, но становит- ся действительным, тогда TCV меняет свое значение на 1. Сложение двух чисел А и В, имеющих типы кодов ТСА и ТСВ, выполняется следующим образом: 1. Если ТС А=ТС В=0, сложить А и В как целые. 2. Если ТСА=ТСВ = 1, сложить А и В как действительные. 3. Если ТСА=1, а ТСВ=0, сложить А и CONVR(B) как действительные, причем CONVR(B) представляет собой дей- ствительное число, соответствующее целому B(CONVR обозна- чает convert to real — преобразовать в действительное). 4. Если ТСА=0, а ТСВ = 1, сложить CONVR(A) и В как . действительные. Вычисления с преобразованием типов могут оказаться слиш- ком расточительными по объему памяти и времени выполнения из-за наличия большого количества кодов типов и необходимо- сти их анализа перед каждой операцией, даже такой простой, как сложение. Однако многие программы выполняются так бы- стро и требуют такую малую часть доступной памяти, что до-
Вычисления с преобразованием типов 363 полнительные возможности обработки с преобразованием типов оправдывают применение этого средства. В более общем случае число типов может быть больше двух. В некоторых языках объекты данных «целое число» и «массив целых чисел» относятся к разным типам; даже массив целых чисел размерности п и массив целых чисел размерности пг=/=п— это разные типы. В языке Лисп, предназначенном для обработки списков, «список» — это тип, а «атом», включающий целые чис- ла— это другой тип. В языке Снобол, предназначенном для обработки строк, «целое» — это один тип, а «строка» — другой. Упражнения 1. В некоторых версиях Бейсика имеются переменные трех типов; действительные переменные, для которых характерны имена, содержащие только буквы и цифры; целые переменные, имена которых оканчиваются символом % и строковые перемен- ные, имена которых оканчиваются символом $. Годится ли об- работка с преобразованием типов для такой версии Бейсика? Почему? 2. В языке программирования APL имена переменных могут представлять массивы, при этом с такими переменными •возможны массивные операции. Например, если А и В — имена массивов, тогда операция присваивания величине А значения А+В эквивалентна присваиванию A(k) значения А(£)+В(£) для всех возможных для данных массивов значений k. Точно так же присваивание величине А значения А,В (здесь запятая используется аналогично оператору +) эквивалентно конкате- нации (сцеплению) массивов А и В; если А содержит ш эле- ментов, а В — п элементов, новый массив А будет состоять из т + п элементов. Годится ли обработка с преобразованием типов для этого языка, если все элементы массивов целые числа? По- чему годится или почему не годится? * 3. Предположим, что в программе для процессора 6502 величине J соответствует код типа TJ. Если TJ = 0, то J — 8-раз- рядная величина без знака. Если TJ = 1, то J—16-разрядная величина без знака с переставленными, как обычно, байтами. Аналогично этому величине К соответствует код типа ТК. Объ- ясните своими словами, как будет происходить выполнение опе- рации J=J + K с использованием преобразования типов. (Здесь надо рассмотреть 4 случая. Заметьте, что операция сложения сама по себе не изменяет код типа величины J.) Задача 6 для решения на ЭВМ Восемь ферзей Наша заключительная задача проще, чем может показаться •с первого взгляда. Рассмотрим шахматную доску, на которой рас-
364 Статья 100 положены 8 ферзей (рис. 100.1). Ни один из ферзей не может атаковать другого (по правилам игры в шахматы), поскольку никакая пара ферзей не находится в одном ряду, в одном столб- Рис. 100.1. так легко; ваша задача — найти все такие позиции (их всего 92) с помощью ЭВМ. В такой позиции, очевидно, в каждом ряду, с 1 по 8, нахо- дится один и только один ферзь (в противном случае два фер- зя атаковали бы друг друга вдоль ряда). Для позиции, изобра- женной на рис. 100.1, можно составить таблицу: Ряд 1 2 3 4 5 6 7 8 Столбец aehfcgbd и обозначить эту позицию «позиция aehfcgbd». Прием, которым мы воспользуемся, заключается в генерации всех таких позиций в алфавитном порядке (под словом «генерация» мы будем по- нимать «нахождение и вывод на экран»). Сначала генерируют- ся все позиции, начинающиеся с а (например, aehfcgbd)-, за- тем — начинающиеся с b и т. д. Среди всех позиций, которые начинаются с а, мы сначала находим позиции, начинающиеся с аа или ab, если таковые имеются; однако их нет, поскольку ферзь в ряду 2 и столбце а или b сможет атаковать ферзя в ря-
Вычисления с преобразованием типов 365 ду 1 и столбце а. (Позиции, начинающиеся с ас или ad также отсутствуют, но на выяснение этого потребуется гораздо боль- ше времени.) Следующий шаг — нумерация диагоналей. Всего на доске имеются 15 диагоналей от верхнего левого угла до нижнего пра- вого (будем называть их диагоналями типа Р) и еще 15 диаго- налей от верхнего правого угла до нижнего левого (это будут диагонали типа Q). Поэтому мы создаем два массива Р и Q длиной 15 байт каждый и инициализируем все элементы этих массивов нулями. Помещая ферзя на диагональ х типа Р и диа- гональ у типа Q, мы устанавливаем Р(х) и Q(y) равными 1, чтобы показать, что эти две диагонали теперь заняты. Перед этим, однако, мы проверяем, не равны ли уже Р(х) или Q(y) единице; если это так, мы не можем поставить ферзя на данное поле, поскольку он будет атакован имеющимся ферзем. Если ферзь удаляется с доски, Р(х) и Q(«/) снова приравниваются нулю. Алгоритм программы заключается в следующем. Поставьте ферзя на доску в ряд 1 и столбец а (выполнив при этом приве- денную выше проверку на возможность атаки по диагонали). Теперь генерируйте все позиции, начинающиеся с а (см. следую- щий абзац). Когда это сделано, удалите ферзя из ряда 1 и столбца а, поместите его в ряд 1 и столбец b и генерируйте все позиции, начинающиеся с b (тем же образом). Продолжайте этот процесс, пока не получите все искомые позиции. Для генерации всех позиций, начинающихся с а, пытайтесь поставить следующего ферзя по очереди на каждый из столбцов а, Ь, с и т. д. в ряду 2. Первое возможное поле — это с. Теперь генерируйте все позиции, начинающиеся с ас, точно таким же образом: пытайтесь поставить ферзя врядЗ на каждый из столб- цов по очереди. Если вы находитесь в ряду i, а столбцы кончи- лись, перейдите на ряд i— 1 (для чего уменьшите I на 1 и затем вернитесь к ряду i уже с новым значением i), удалите ферзя, стоящего в этом ряду (если только не получилось i=0, что гово- рит о завершении программы), и попробуйте поместить ферзя на следующую разрешенную позицию в том же ряду, после той по- зиции, в которой он находился. ' Получив требуемую позицию (вроде той, которая приведе- на на рис. 100.1), выведите ее на экран (с помощью подпро- граммы COUT) и продолжайте поиск следующих позиций в точ- ности так же, как если бы вы не нашли требуемую позицию. Целесообразно написать эту программу на Бейсике, отладить ее (убедившись при этом, что генерируются все 92 позиции) и лишь затем переписать на язык ассемблера.
Приложение Таблица П1. Сопоставление Бейсика с языками Фортран, ПЛ/1 и Паскаль Назначение Пробелы Условные предложения Константы END GOSUB GOTO Ввод-вывод Итерация Номера строк v=et где v — переменная, а е — выражение. Подобно- v=e в Фортране; подобно и=е на ПЛ/1; подобно- v: =е в языке Паскаль. В Бейсике не используется- символ точки с запятой и сочетание двоеточия со зна- ком равенства. В отличие от Фортрана (и Бейсика системы Эпл) стан- дартный Бейсик не игнорирует любые пробелы. Пробе- лы не могут появляться внутри имен переменных. (В языке ассемблера пробелы могут появляться только- там, где они явно разрешены.) IF с THEN и (где п — номер строки (см. ниже)). По- добно IF с THEN GO ТО п в Фортране; подобно IF с THEN GOTO п; в ПЛ/1 (см. GOTO ниже); подобно» IF с THEN GOTO п в языке Паскаль. IF с THEN s (где s — предложение). Подобно IF (с) s в Фортране;, подобно IF с THEN $; в ПЛ/1; подобно IF с THEN s в языке Паскаль. Если не считать некоторых расширенных версий, разни- ца между целыми и действительными константами в Бейсике отсутствует. Имеются также строковые кон- станты (не обсуждаемые здесь). В пределах изложенных сведений подобно END в язы- ках Фортран, ПЛ/1 или Паскаль. GOSUB п подобно CALL S в Фортране или ПЛ/1, ил» S (само по себе) в языке Паскаль, где 5 начинается на строке п. В Бейсике отсутствует понятие параметров подпрограмм. Подобно GOTO в языках Фортран, ПЛ/1 или Паскаль^ только на ПЛ/1 следует писать GOTO и; (где v — метка (см. ниже Номера строк)). В языке Паскаль не поощряется использование GOTO; в Бейсике этого нет. Предложения INPUT и PRINT; нас они не будут ин- тересовать. FOR v = ТО п, далее следует группа предложений g, далее следует NEXT v. Подобно DO k v=m, nr далее следует g (последнее предложение имеет номер п) в Фортране. Подобно DO v=m ТО n; g; END; в ПЛ/1; подобно FOR v: =/n ТО n DO BEGIN g END в языке Паскаль. В Бейсике имеется также FOR v — m ТО и STEP j, но этот вариант нас не будет ин- тересовать. В программе на Бейсике должны следовать в порядке возрастания. В противном случае они подобны номерам предложений в программе на Фортране, или меткам языка Паскаль, или меткам в ПЛ/1 (но на Бейсике они — целые числа). 366
Продолжение Операторы отношений RETURN Имена переменных <>< = > = = < > подобно .LT. .GT. .LE. .GE. .EQ. jNE. в Фортране; подобно LT GT LE GE = “* — на ПЛ/1. Подобно RETURN в ПЛ/1 (но нет формы RETURN (е)) ив Фортране. В языке Паскаль возврат из под- программы осуществляется после выполнения ее послед^ него предложения.) Только 1 или 2 символа, кроме расширенных версий Бейсика. Массивы объявляются с помощью DIM t>(s), подобно DIMENSION v(s) в Фортране, VAR v\ ARRAY [диапазон] OF t в языке Паскаль, или DCL v(s) в ПЛ/1. Таблица П2 а) Преобразование шестнадцатеричных чисел в десятичные Четвертая цифра Третья цифра Вторая цифра Первая цифра 0 0 0 0 0 1 4096 256 16 1 2 8192 512 32 2 3 12288 768 48 3 4 16384 1024 64 4 5 20480 1280 80 5 6 24576 1536 96 6 7 28672 1792 112 7 8 32768 2048 128 8 9 36864 2304 144 9 А 40960 2560 160 10 В 45056 2816 176 11 С 49152 3072 192 12 D 53248 3328 208 13 Е 57344 3584 224 14 F 61440 3840 240 15 367
б) Преобразование десятичных чисел в шестнадцатеричные Пятая цифра Четвертая цифра Третья цифра Вторая цифра Первая цифра 0 0 0 0 0 0 1 2710 ЗЕ8 64 А 1 2 4Е20 7D0 С8 14 2 3 7530 ВВ8 12С 1Е 3 4 9С40 FA0 190 28 4 5 С350 1338 1F4 32 5 6 ЕА60 1770 258 ЗС 6 7 11170 1В58 2ВС 46 7 8 13880 1F40 320 50 8 9 16F90 2328 384 5А 9 Пример Для преобразования шестнадцатерич- ного числа B7DC в десятичную фор- му, найдите: Для преобразования десятичного чис- ла 47 068 в шестнадцатеричную фор- му, найдите: В под четвертой цифрой — 45 056 7 под третьей цифрой — 1 792 D под второй цифрой — 208 С под первой цифрой — 12 Затем сложите. Результат — 47 068 4 под пятой цифрой — 9С40 7 под четвертой цифрой — 1В58 6 под второй цифрой — ЗС 8 под первой цифрой — 8 Затем сложите. Результат — B7DC Таблица ПЗ. Команды процессора 6502. Мнемоника, описание и установка флажков Номер статьи Флажки Мнемо- ника Расшифровка мнемоники и описание команды 15 ZSCV ADC v Add with carry — сложение с переносом. Устанав- ливает А=А+^+С. 52 ZS AND v Logical AND — логическое И. Устанавливает А=АИи. 31 ZSC ASL v Arithmetic shift left — арифметический сдвиг вле- во. Устанавливает и=2>|су (если v отсутствует, устанавливает А=2>|<А). 19 ВСС L Branch on carry clear — переход, если флажок пе- реноса сброшен. Выполняет переход на L, если С=0. 368
Таблица ПЗ (продолжение) Номер Флажки Мнемо- Расшифровка мнемоники статьи ника и описание команды 19 — BCS L Branch on carry set — переход, если флажок пе- реноса установлен. Выполняет переход на L, если С=1. 20 — BEQ L Branch on equal — переход по равенству. Выпол- няет переход на L, если Z=l. 55 ZSV BIT v Bit test — проверка битов. Вычисляет А И у (операция логического И); устанавливает Z; пе- ресылает в S и V два левых бита v (соответ- ственно) . 28 — BMI L Branch on minus — переход по минусу. Выполня- ет переход на L, если S=l. 20 — BNE L Branch on not equal — переход по неравенству. ^Выполняет переход на L, если Z=0. 28 — BPL L Branch on plus — переход по плюсу. Выполняет переход на L, если S = 0. 42 в BRK Break — прерывание. Передает управление мони- тору. 56 — BVC L Branch on overflow clear — переход, если флажок переполнения сброшен. Выполняет переход на L, если V=0. 56 — BVS L Branch on overflow set — переход, если флажок переполнения установлен. Выполняет переход на L, если V= 1. 15 с CLC Clear carry — сброс флажка переноса. Устанав- ливает С = 0. 65 D CLD Clear decimal mode — выключение десятичного ре- жима. Устанавливает D = 0. 68 I CLI Clear interrupt flag —сброс флажка прерываний. Устанавливает 1 = 0 (разрешает прерывания). 56 V CLV Clear overflow flag — сброс флажка переполне- ния. Устанавливает V=0. 20 ZSC CMP V Compare (with А)—сравнение (с А). Вычисляет А — и; устанавливает флажки. 20 ZSC CPX v Compare with X — сравнение с X. Вычисляет X—у; устанавливает флажки. 20 ZSC CPY v Compare with Y — сравнение с Y. Вычисляет Y—и; устанавливает флажки. 9 ZS DEC v Decrement memory — декремент в памяти. Уста- навливает v~v— 1. 9 ZS DEX Decrement X — декремент X. Устанавливает Х=Х-1. 9 ZS DEY Decrement Y — декремент Y. Устанавливает Y= = Y—1. • 54 ZS EOR v Exclusive OR —ИСКЛЮЧАЮЩЕЕ ИЛИ. Уста- навливает А=А ИСКЛЮЧАЮЩЕЕ ИЛИ v. 24—589 369
Таблица ПЗ (продолжение) Номер статьи Флажки Мнемо- ника Расшифровка мнемоники и описание команды 9 ZS INC v Increment memory — инкремент в памяти. Уста- навливает У=2>+1. 9 ZS INX Increment X — инкремент X. Устанавливает Х= = Х+1. 9 ZS INY Increment Y—инкремент Y. Устанавливает Y= =Y+1. 20 — JMP L Jump — переход. Выполняет переход на L (пря- мой или косвенный). 25 — JSR L Jump to subroutine — переход на подпрограмму. Вызывает L. 8 ZS LDA v Load А — загрузка А. Устанавливает A=v. 8 ZS LDX v Load X — загрузка X. Устанавливает X=v. 8 ZS LDY v Load Y — загрузка Y. Устанавливает Y=v. 31 ZSC LSR v Logical shift right — логический сдвиг вправо. Ус- танавливает и = у/2 (беззнаковое деление; если v не указано, устанавливает А=А/2). 50 — NOP No operation — операция отсутствует, не выпол- няет никаких действий. 53 ZS ORA Logical OR — логическое ИЛИ. Устанавливает А=А ИЛИ V. 60 •— PHA Push А — проталкивание А. Сохраняет А в стеке. 67 — PHP Push Р — проталкивание Р. Сохраняет Р в стеке. 60 ZS PLA Pull А — выталкивание А. Восстанавливает А из стека. 67 все PLP Pull Р — выталкивание Р. Восстанавливает Р из стека. 34 ZSC ROL v Rotate left — циклический сдвиг влево. Устанав- ливает y=2>fcv+C (если v не указано, устанав- ливает А=2>|<А+С). 34 ZSC ROR v Rotate right — циклический сдвиг вправо. Уста- навливает v==(256*C+u)/2 (если v не указано» устанавливает А= (256>|<С+А)/2). 68 все RTI Return from interrupt — возврат из прерывания. Осуществляет возврат из программы обработки прерывания в выполнявшуюся программу. 39 — RTS Return from subroutine — возврат из подпрограм- мы. Осуществляет возврат из подпрограммы в вызывающую программу. 17 ZSCV SBC v Subtract with carry — вычитание с переносом. Ус- танавливает А= (А—у) + (1—С). 17 с SEC Set carry — установка флажка переноса. Устанав- ливает С= 1. 65 D SED Set decimal mode — включение десятичного режи- ма. Устанавливает D = l. 370
Таблица ПЗ (продолжение) Номер статьи Флажки Мнемоника Расшифровка мнемоники и описание команды 68 I SEI Set interrupt flag — установка флажка прерыва- ний. Устанавливает 1=1 (запрещает прерыва- ния). 8 — STA v Store А — запись А. Устанавливает v=A. 8 — STX v Store X — запись X. Устанавливает у=Х. 8 — STY v Store Y — запись Y. Устанавливает u=Y. 18 ZS ТАХ Transfer A to X — пересылка А в X. Устанавлива- ет Х=А. 18 ZS TAY Transfer A to Y — пересылка А в Y. Устанавлива- ет Y=A. 60 ZS TSX Transfer SP to X —пересылка SP в X. Устанав- ливает X=SP. 18 ZS ТХА Transfer X to A — пересылка X в А. Устанавли- вает A=X. 60 — TXS Transfer X to SP — пересылка X в SP. Устанав- ливает SP=X. 18 ZS TYA Transfer Y to A — пересылка Y в А. Устанавли- вает A=Y. В графе «Флажки» указаны флажки, устанавливаемые данной командой. Машин- ное представление и время выполнения команд приведены в табл. П4. Таблица П4. Форматы команд на языке ассемблера и на машинном языке и время выполнения команд Принятые обозначения: L и Q имеют адрес abed; Z имеет адрес OOef; п имеет шестнадцатеричное значение gfv, jk удовлетворяет условию AI+2+/£=AL, где AI —адрес этой команды, a AL — адрес L. Язык ассемблера Число машинных циклов 3 Машинный язык Язык ассемблера Число машинных циклов 8 Машинный язык ADC Q 4 6D cd ab AND Q 4 2D cd ab ADC Z 3 65 ef AND Z 3 25 ef ADC #n 2 69 gh AND #n 2 29 gh ADC Q, X 40 7D cd ab AND Q, X 40 3D cd ab ADC Q, Y 40 79 cd ab AND Q, Y 40 39 cd ab ADC (Z, X) 6 61 ef AND (Z, X) 6 21 ef ADC (Z), Y 50 71 ef AND (Z),Y 5‘) 31 ef ADC Z, X 4 75 ef AND Z, X 4 35 ef 24* 371
Таблица П4 (продолжение) Язык ассемблера Число машинных циклов 3 Машинный язык Язык ассемблера Число машинных циклов 8 Машинный язык ASL 2 OA EOR Q, Y 40 59 cd ab ASL Q 6 OE cd ab EOR (Z, X) 6 41 ef ASL Z 5 06 ef EOR (Z),Y 50 51 ef ASL Q, X 7 IE cd ab EOR Z, X 4 55 ef ASL Z, X 6 16 ef INC Q 6 EE cd ab j ВСС L 32> 90 jk INC Z 5 E6 ef BCS L 32> во jk INC Q, X 7 FE cd ab BEQ L 32> FO jk INC Z,X 6 F6 ef BIT Q 4 2C cd ab INX 2 E8 BIT z 3 24 ef INY 2 C8 BMI L 32> 30 jk JMP L 3 4C cd ab BNE L 32> DO jk JMP (L) 5 6C cd ab BPL L 32> 10 jk JSR L 6 20 cd ab BRK 7 00 LDA Q 4 AD cd ab BVC L 32> 50 jk LDA Z 3 A5 ef BVS L 32> 70 jk LDA #n 2 A9 gh CLC 2 18 LDA Q, X 40 BD cd ab CLD 2 D8 LDA Q, Y 40 B9 cd ab j CLI 2 58 LDA (Z,X) 6 Al ef 1 CLV 2 B8 LDA (Z),Y 50 Bl ef 1 CMP Q 4 CD cd ab LDA Z, X 4 B5 ef 1 CMP Z 3 C5 ef LDX Q 4 AE cd ab I CMP #n 2 C9 gh LDX Z 3 A6 ef j CMP Q, X 40 DD cd ab LDX #n 2 A2 gh CMP Q, Y 40 D9 cd ab LDX Q,Y 40 BE cd ab ; CMP (Z, X) 6 Cl ef LDX Z,Y 4 B6 ef CMP (Z),Y 51 DI ef LDY Q 4 AC cd ab CMP Z, X 4 D5 ef LDY Z 3 A4 ef CPX Q 4 EC cd ab LDY #n 2 AO gh CPX Z 3 E4 ef LDY Q, X 40 BC cd ab CPX 2 EO gh LDY Z, X 4 B4 ef CPY Q 4 CC cd ab LSR 2 4A j CPY Z 3 C4 ef LSR Q 6 4E cd ab CPY #n 2 CO gh LSR Z 5 46 ef DEC Q 6 CE cd ab LSR Q, X 7 5E cd ab DEC Z 5 C6 ef LSR Z, X 6 56 ef DEC Q, X 7 DE cd ab NOP 2 EA DEC Z, X 5 C6 ef ORA Q 4 OD cd ab DEX 2 CA ORA Z 3 05 ef DEY 2 88 ORA #n 2 09 gh EOR Q 4 4D ca ab ORA Q, X 40 ID cd ab ‘ EOR Z 3 45 ef ORA Q, Y 40 19 cd ab EOR 2 49 gh ORA (Z, X) 6 01 ef 1 EOR Q, X 40 5D cd ab ORA (Z),Y 50 11 ef f i 372 i
Таблица П4 (продолжение) Язык ассемб- лера Число ма- шинных цикловЗ Машинный язык Язык ассемб- лера Число ма- шинных цикловЗ Машинный, язык ORA Z,X 4 15 ef SBC Z,X 4 F5 ef РНА 3 48 SEC 2 38 РНР 3 08 SED 2 F8 PLA 4 68 SEI 2 78 PLP 4 28 STA Q 4 8D cd ab ROL 2 2A STA Z 3 85 ef ROL Q 6 2E cd ab STA Q, X 5 9D cd ab ROL Z 5 26 ef STA Q,Y 5 99 cd ab ROL Q, X 7 3E cd ab STA (Z,X) 6 81 ef ROL Z, X 6 36 ef STA (Z),Y 6 91 ef ROR 2 6A STA Z,X 4 95 ef ROR Q 6 6E cd ab STX Q 4 8E cd ab ROR Z 5 66 ef STX Z 3 86 ef ROR Q, X 7 7E cd ab STX Z,Y 4 96 ef ROR Z,X 6 76 ef STY Q 4 8C cd ab RTI 6 40 STY Z 3 84 ef RTS 6 60 STY Z, X 4 94 ef SBC Q 4 ED cd ab TAX 2 AA SBC Z 3 E5 ef TAY 2 A8 SBC #n 2 E9 gh TSX 2 BA SBC Q, X 4‘) FD cd ab TXA 2 8A SBC Q,Y 4*) F9 cd ab TXS 2 9A SBC (Z,X) 6 El ef TYA 2 98 SBC (Z),Y 51* Fl ef Описание команд и условия установки флажков см. табл. ПЗ; описание режимов адресации см. табл. П8; машинные формы команд в порядке возрастания кодов см. табл. П5. 1 Плюс 1» если прибавление к адресу индекса приводит к переносу в старший байт адреса. 2 Минус 1, если команда не осуществляет переход; плюс 1, если прибавление отно« си тельного адреса со знаком приводит к увеличению или уменьшению старшего байта адреса на 1. 8 1 машинный цикл = 0.9775 мкс; 1 023 000 машинных циклов = 1 с. Таблица П5. Машинные формы команд в порядке возрастания кодов Принятые обозначения: L и Q имеют адрес abcd\ Z имеет адрес OOef; п имеет шестнадцатеричное значение dh\ jk удовлетворяет условию AI+2+/£=AL, где AI — адрес этой команды, a AL —адрес L. Машинный язык Язык ассемблера Машинный язык Язык ассемблера 00 BRK 06 ef ASL Z 01 ef ORA (Z,X) 08 PHP 05 ef ORA Z II 09 gh ORA =Ц=п 373
Таблица П5 (продолжение) ЭДашиный язык Язык ассемблера Машиный язык Язык ассемблера ОА ASL 56 ef LSR Z, X OD cd ab ORA Q 58 CLI ОЕ cd ab ASL Q 59 cd ab EOR Q,Y 10 jk BPL L 5D cd ab EOR Q, X 11 ef ORA (Z),Y 5E cd ab LSR Q, X 15 ef ORA Z, X 60 RTS 16 ef ASL Z, X 61 ef ADC (Z, X) 18 CLC 65 ef ADC Z 19 cd ab ORA Q, Y 66 ef ROR Z ID cd ab ORA Q, X 68 PLA IE cd ab ASL Q, X 69 gh ADC #n 20 cd ab JSR L 6A ROR 21 ef AND (Z,X) 6C cd ab JMP (L) 24 ef BIT z 6D cd ab ADC Q 25 ef AND Z 6E cd ab ROR Q 26 ef ROL Z 70 jk BVS L 28 PLP 71 ef ADC (Z),Y 29 gh AND #n 75 ef ADC Z,X 2A ROL 76 ef ROR Z,X 2C cd ab BIT Q 78 SEI 2D cd ab AND Q 79 cd ab ADC Q, Y 2E cd ab ROL Q 7D cd ab ADC Q, X 30 jk BMI L 7E cd ab ROR Q, X 31 ef AND (Z),Y 81 ef STA (Z,X) 35 ef AND Z,X 84 ef STY Z 36 ef ROL Z,X 85 ef STA Z 38 SEC 86 ef STX Z 39 cd ab AND Q,Y 88 DEY 3D cd ab AND Q, X 8A TXA 3E cd ab ROL Q, X 8C cd ab STY Q . 40 RTI 8D cd ab STA Q 41 ef EOR (Z,X) 8E cd ab STX Q 45 ef EOR Z 90 jk BCC L 46 ef LSR Z 91 ef STA (Z),Y 48 PHA 94 ef STY Z,X v 49 gh EOR #n 95 ef STA Z,X 4A LSR 96 ef STX Z,Y - 4C cd ab JMP L 98 TYA 4D cd ab EOR Q 99 cd ab STA Q,Y 4E cd ab LSR Q 9A TXS 50 jk BVC L 9D cd ab STA Q, X 51 ef EOR (Z),Y A0 gh LDY #n 55 ef EOR Z, X Al ef LDA (Z.X) 374
Таблица П5 (продолжение) Машинный язык Язык ассемблера Машинный язык Язык ассемблера’ А2 gh LDX #n CD cd ab CMP Q А4 ef LDY Z CE cd ab DEC Q А5 ef LDA Z DO jk BNE L А6 ef LDX Z DI ef CMP (Z),Y A8 TAY D5 ef CMP Z,X A9 gh LDA #n D6 ef DEC Z,X AA TAX D8 CLD AC cd ab LDY Q D9 cd ab CMP Q,Y AD cd ab LDA Q DD cd ab CMP Q, X AE cd ab LDX Q DE cd ab DEC Q, X BO jk BCS L EO gh CPX #n Bl ef LDA (Z),Y El ef SBC (Z,X) B4 ef LDY Z,X E4 ef CPX z B5 ef LDA Z,X E5 ef SBC z B6 ef LDX Z,Y E6 ef INC Z B8 CLV E8 INX B9 cd ab LDA Q,Y E9 gh SBC #n BA TSX EA NOP BC cd ab LDY Q, X EC cd ab CPX Q BD cd ab LDA Q, X ED cd ab SBC Q BE cd ab LDX Q,Y EE cd ab INC Q CO gh CPY #n FO jk BEQ L Cl ef CMP (Z,X) Fl ef SBC (Z),Y C4 ef CPY Z F5 ef SBC Z,X C5 ef CMP z F6 ef INC Z, X C6 ef DEC Z F8 SED C8 INY F9 cd ab SBC Q,Y C9 gh CMP #n FD cd ab SBC Q, X CA DEX FE cd ab INC Q, X CC cd ab CPY Q Описание команд и условия установки флажков см. табл. ПЗ; команды языка ас- семблера в алфавитном порядке см. табл. П4; описание режимов адресации см. табл. П8. Таблица П6. Псевдокоманды и расширенная мнемоника системы LISA Псевдо- команда Статья КНИГИ Описание (Замечание: {метка} обозначает необязательную метку; метка обозначает обязательную метку; в противном случае метка отсутствует.) {метка} ADR a 63 Записывает 16-разрядную величину а (с символической адресацией) в память с пе- реставленными байтами (сначала правый байт, затем левый байт). 375
Таблица 176 (продолжение) Псевдо- команда Статья книги Описание (Замечание: {метка} обозначает необязательную метку; метка обозначает обязательную метку; в противном случае метка отсутствует.) {метка} ASC s 26 Записывает строку s в память, причем в самый левый разряд каждого байта зано- сится 0, если s имеет вид усссс...с', и 1, если s имеет вид "сссс...с". {метка} BFL k 84 Переход на метку k по условию «ложно» эквивалентно BEQ k. {метка} BGE k 28 Переход на метку k по условию «больше или равно» эквивалентно BCS k. {метка} BLK s 73 Записывает строку з в память, причем в 2 левых разряда каждого байта записыва- ется 01 (режим мерцания — символы «мер- цают» на экране). {метка} BLT k 28 Переход на метку k по условию «меньше чем» эквивалентно ВСС k. {метка} BTR k 84 Переход на метку k по условию «истина»; эквивалентно BNE k. {метка} BYT a 26 Записывает правые 8 бит 16-разрядной величины а (с символической адресацией) в память. {метка} DCI s 73 Записывает строку s в память, при этом старший бит последнего байта строки от- личен от старших битов остальных байтов (см. ASC). DCM "c" 92 Команды ДОС системы LISA (DCM "OPEN f" и DCM "WRITE"/" в начале программы и DCM "CLOSE" перед END образуют на диске файл f с листингом). {метка} DFS n 11, 13 Резервирует в этом месте программы п байт для переменной или массива с меткой метка (и не присваивает им начальное значение). END 11 Последнее предложение в программе (в каждой программе одна и только одна псевдокоманда END). метка EPZ a 74 Устанавливает значение метки эквивалент- но (см. ниже EQU) значению а на нуле- вой странице. метка EQU a 25 Устанавливает значение метки метка экви- валентно значению а («Эквивалентность» означает: всюду, где в программе появля- ется метка, программа выполняется так. как если бы вместо метка стояла а.) 376
Таблица П6 (продолжение) Описание (Замечание: {метка} обозначает Псевдо- Статья необязательную метку; метка обозначает обя- команда КНИГИ зательную метку; в противном случае метка отсутствует.) {метка} HBY a 63 Записывает левые 8 бит 16-разрядной ве- личины а (с символической адресацией) в память. {метка} HEX h 73 Записывает строку h шестнадцатеричных цифр (не в кавычках) в память, по 2 шест- надцатеричных цифры в каждом байте. {метка} INV s 73 Записывает строку s в память, причем в 2 левых-разряда каждого байта записыва- ется 00 (инверсный режим — черным по белому). LST 92 Включает опцию выдачи листинга (нор- мально включено; см. NLS). NLS 92 Выключает опцию вывода листинга, так что ассемблируемые команды не будут выво- диться в листинг, пока не встретится LST. OBJ a 51 Указывает, что программа должна ассемб- лироваться в памяти, начиная с адреса а, обычно $0800, независимо от операнда псевдокоманды ORG (см. ниже). ORG a 11 Указывает, что программа при выполнении должна начинаться с адреса а. (Может быть ассемблирована в другое место с по- мощью псевдокоманды OBJ и перемещена позднее.) PAG 92 Перевод страницы на печатающем устрой- стве. При выводе листинга на печатающее устройство начнет печать с начала новой страницы. метка STR s 73 Записывает строку s в память и помещает перед ней байт длины, старший бит кото- рого совпадает со старшими битами бай- тов строки (LISA 1.5), или 8-битовый байт длины (LISA 2.5; см. псевдокоманду ASC). метка XOR V 54 ИСКЛЮЧАЮЩЕЕ ИЛИ с и; то же, что EOR v (опции для и те же, что и в EOR v). 377
Таблица П7. Описание специальных символов в системе LISA Символ Статья книги Описание Звездочка * 30 Текущий адрес (ALPHA BEQ *4-15 экви- валентно ALPHA BEQ ALPHA-H5). Возврат •*- 46 Для возврата через символы для исправ- ления ошибок. Двоеточие : 81 Следует за меткой, если на данной строке нет ничего кроме метки. Запятая , 13 Для индексирования (,Х обозначает ин- дексный регистр X; и ,У обозначает ин- дексный регистр Y). Control-О, J, К, L 46 Эти символы позволяют перемещать курсор по экрану для исправления ошибок. Знак доллара $ 8 Ставится перед шестнадцатеричными чис- лами ($80 обозначает шестнадцатерич- ное 80, или десятичное 128). В оск лицате льный знак ! 8 Ставится перед десятичными числами (!128 обозначает десятичное 128, или шестнад- цатеричное 80). Дефис — (минус) 12,23 Вычитание (если Т — ячейка с адресом л, то Т — !к — ячейка с адресом п—k). Знак номера # 8 Непосредственная адресация (LDA #л обозначает загрузку числа п или левой половины 16-разрядной константы л). Скобки () 58, 75 76 Косвенная адресация (JMP (L) или ор (Z, X), или op (Z), Y, где ор — команды LDA, STA, ADC, SBC, СМР, AND, ORA, EOR). Знак процента % 8 Ставится перед двоичным числом (% 10000000 обозначает двоичное 10000000, или шестнадцатеричное 80, или десятич- ное 128). Знак плюс + 12, 23 Сложение (если Т — ячейка с адресом л, то Т+!& — ячейка с адресом л+£). Кавычки " (двойные) 24 'сссс...с' — это строка ссс...с, в которой каждая пара двойных кавычек " заменя- ется одним знаком двойных кавычек, а ле- вый бит каждого байта равен единице; "с — краткое обозначение # "с". Кавычки ' (одиночные) 24 'сссс...с' — это строка ссс...с, в которой каждая пара одиночных кавычек ' заменя- ется одним знаком одиночных кавычек, а левый бит каждого байта равен нулю; 'с — краткое обозначение ф'с'. Повторение (REPT) 46 Нажатие любой клавиши k и REPT экви- валентно kkkk... Перепечатка —> (правая стрелка) 46 После исправления ошибок позволяет включить в файл оставшуюся часть (пред- положительно правильную) текста на строке. Точка с запятой ; 18 Пробел, за которым стоит точка с запятой (или точка с запятой в позиции 1), начи- нает комментарий (игнорируемый ассемб- лером). 378
Таблица П7 (продолжение) Символ Статья книги Описание Слэш / 58 Непосредственная адресация старшего бай- та (LDA/n обозначает загрузку старшей половины 16-разрядной константы л). Пробел 19 Один или более пробелов до и после мне- моники команды на каждой строке (а так- же знак точки с запятой, если он исполь- зуется) . Таблица П8. Команды процессора 6502 — описание способов адресации Способ адресации Статья Описание Обозначение Название книги L(JMP,JSR) L (осталь- ные) #П Нет Q (Q) Q,x Q.Y 2 Абсолютная Относительная Непосредст- венная Заключено в мнемонике или адресация к аккумулятору Абсолютная (расширенная) Косвенная (не индексная) Абсолютная с индексацией че- рез регистр X Абсолютная с индексацией через регистр Y Адресация к нулевой стра- нице (прямая) 27 27 8 10 8 58 13 13 74 Переход на ячейку с адресом abed, где cd — второй, a ab — третий бай- ты команды. Переход1) на ячейку с адресом pqrs+2+jk, причем настоящая ко- манда находится по адресу pqrs и pqrs+1, jk — постоянная со знаком, расположенная по адресу pqrs+1. Обращение к числу во втором байте команды. Обращения к операнду не требуется; или в случае сдвигов сдвиг выпол- няется в регистре А. Обращение к содержимому ячейки с адресом abed, где cd — второй, a ab — третий байты команды. Обращение к содержимому ячейки с адресом pqrs, причем ячейка abed определена, как и ранее, и содержит rst а ячейка abcd+1 содержит pq. Обращение к содержимому ячейки с адресом abed+xx, где abed опреде- лено, как и ранее, а хх находится в регистре X. Обращение к содержимому ячейки с адресом abed+yy, где abed опреде- лено, как и ранее, а уу находится в регистре У. Обращение к содержимому ячейки с адресом OOef+xx, где ef — второй байт команды. 379
Таблица П8 (продолжение) Способ адресации Статья книги Описание Обозначение Название z,x Адресация к нулевой стра- нице с индекса- цией через ре- гистр X 74 Обращение к содержимому ячейки с адресом OOef+хх, где ef — опреде- лено, как и ранее, а хх находится в регистре X. (Z.X) Предындексная косвенная 75 Обращение к содержимому ячейки с адресом pqrs, причем ячейка 00ef+xx (ef и хх определены, как и ранее) содержит rst а ячейка OOef-j-xx+1 содержит pq. Z, Y Адресация к нулевой страни- це с индексаци- ей через ре- гистр Y 74 Обращение к содержимому ячейки OOef+yy, причем ef определено, как и ранее, а уу находится в регистре У (Z). Y Постиндексная косвенная 76 Обращение к содержимому ячейки с адресом pqrs+yy, причем ячейка OOef (ef определено, как и ранее) содержит rs, ячейка OOef+1 содер- жит pq, а уу находится в регистре У. Эта таблица поясняет нотацию, использованную в таблицах П4 и П5. Обратите внимание на то, что X и Y должны быть именно символами X и Y, в то время как L, Q и 2 могут быть заменены любой меткой. ’) С адресацией относительно содержимого счетчика команд. — Прим, перев. Таблица П9. Символьные коды ЭВМ Эпл — буквы и цифры Символьные коды 1 1 Нормальный Управляю- щий Инверсный Мерцающий Нижний регистр (не- обязательно) А С1 81 01 41 Е1 В С2 82 02 42 Е2 С СЗ 83 03 43 ЕЗ D С4 84 04 44 Е4 Е С5 85 05 45 Е5 F С6 86 06 46 Е6 G С7 87 07 47 Е7 н С8 88 08 48 Е8 I J С9 89 09 49 Е9 СА 8А 0А 4А ЕА Символьные коды 2 s ё CS 2 щий . 3 а> д О. • Л к § 0 2 ч а 2 о. 0 ч 0 0 _ Л*? s 2 0 tf О. «3 5 0 г* ® о.2 0 о и S. 0 <D и X >> Я X X ® о к СВ 8В ов 4В ЕВ L сс 8С ОС 4С ЕС М CD 8D 0D 4D ED N СЕ 8Е 0Е 4Е ЕЕ О CF 8F OF 4F EF Р D0 90 10 50 F0 Q D1 91 11 51 F1 R D2 92 12 52 F2 S D3 93 13 53 F3 Т D4 94 14 54 F4 и D5 95 15 55 F5 V D6 96 16 56 F6 W D7 97 17 57 F7 X D8 98 18 58 F8 Y D9 99 19 59 F9 Z DA 9А 1А 5А FA
Продолжение | Символ Символьные коды Нормальный Инверсный Мерцающий 0 ВО 30 70 1 В1 31 71 .2 В2 32 72 3 ВЗ 33 73 4 В4 34 74 5 В5 35 75 6 В6 36 76 7 В7 37 77 8 В8 38 78 9 В9 39 79 Управляющие коды с закрепленными значениями Возврат на шаг 88 Control-Н Звуковой сигнал 87 Control-G Возврат каретки 8D Control-М Переключение ко- да (ESC) 9В Control- [О Левая стрелка 88 Control-H Правая стрелка *) См. табл. Ш0. 95 Control-U Таблица П10. Символьные коды ЭВМ Эпл — специальные символы Символ Нормаль- ный «Г Управ- ляющий Инверс- ный Мерцаю- щий Амперсанд & А6 26 66 Апостроф / А7 27 67 Звездочка * АА 2А 6А Знак эт СО 80 00 40 Обратный слэш DC 9С Диакритический знак DE 9Е Двоеточие ВА ЗА 7А Запятая АС 2С 6С Знак деления / AF 2F 6F Знак доллара А4 24 64 Знак равенства = BD 3D 7D Восклицательный знак 1 А1 21 61 381
Таблица П10 (продолжение) Символ Нормаль- ный Управляю- щий Инверс- ный Мерцаю- щий Знак больше чем BE ЗЕ 7Е Дефис - AD 2D 6D Левая квадратная скобка [ DB 9В Левая скобка ( А8 28 68 Знак меньше чем ВС ЗС 7С Знак минус — AD 2D 6D Знак номера АЗ 23 63 Знак процента % А5 25 65 Точка АЕ 2Е 6Е Знак плюс + АВ 2В 6В Вопросительный знак ? BF 3F 7F Кавычки двойные // А2 22 62 Кавычки одинарные г А7 27 67 Инверсный слэш DC 9С Правая квадратная скобка 1 DD 9D Правая скобка ) А9 29 69' Точка с запятой ВВ ЗВ 7В Слэш / AF 2F 6F Пробел АО 20 60 Знак подчеркивания DF 9F Символы возврата на шаг, звукового сигнала, возврата каретки, переключения кода (ESC), левой и правой стрелок см. в табл. П9. Таблица ПИ. Подпрограммы монитора Эпл и другие специальные адреса Имя Шестнад- цатерич- ный адрес Статья в книге Может изменять содержи- * мое ре- гистров Описание BELLI $FBD9 72 А, X Дает звуковой сигнал на звуковом устройстве Эпл (1/10с). COUT $FDED 25 нет Выводит содержимое регистра А в виде одного символа (обычно на экран; это назначение может быть изменено). COUT1 $FDF0 91 нет Выводит содержимое регистра А на экран (только). 382
Таблица ПИ (продолжение) Имя Шестнад- цатерич- ный адрес Статья в книге Может изменять содержи- мое ре- гистров Описание CROUT SFD8E 251) А Выводит возврат каретки ($8D). GETLN $FD6A 251* А, X, Y Выводит символ-подсказку (обыч- но но хранится в PROMPT — см. ниже), затем вводит одну строку (см. GETLN1). GETLN1 $FD6F 74 А, X, Y Вводит в буфер INBUF (см. ниже) одну строку символов с клавиатуры, которая заканчивается (и включает в себя) возвратом каретки; длина строки в регистре X. GETLNZ $FD67 25 А, X, Y Выводит возврат каретки, а затем вызывает GETLN (так что новая строка появляется на экране, начи- ная с его левого края). INBUF $0200 25 — Стандартный входной буфер; массив из 256 символов. INIT $FB2F 42 — Начало монитора; команду JMP INIT можно использовать для завер- шения основной программы. IOREST $FF3F 51 все Восстанавливает регистры А, X, Y и Р (не S), сохраненные подпрограм- мой IOSAVE. IOSAVE $FF4A 51 А, X Сохраняет содержимое регистров А, X, Y, Р и S в ячейках от $45 до $49. KEYIN $FD1B 91 А, X, Y Вводит один символ с клавиатуры в регистр А. PRBL2 $F94A 41 X Выводит п пробелов с помощью под- программы COUT; при входе п на- ходится в регистре X (если при входе в X нуль, п=256). PRBYTE JFDDA 82 А Выводит содержимое регистра А как две шестнадцатеричные цифры. PRHEX $FDE3 82 А Выводит содержимое регистра А (только правые 4 бита) как шест- надцатеричную цифру. PRNTAX SF941 82 А Выводит содержимое регистра А как две шестнадцатеричные цифры, а за- тем содержимое регистра X как две шестнадцатеричные цифры. 383
Таблица ПН (продолжение) Имя Шестнад- цатеричны f адрес Статья в книге Может из- менять со- держимое регистров Описание PROMPT $0033 74 — Место хранения символа-подсказки, выводимого подпрограммой GETLN (см. выше). RDKEY $FD0C 25 А, X, Y Вводит один символ в регистр А (обычно с клавиатуры; это назначе- ние может быть изменено). RNDH $004F 99 —— Случайное число (старший байт); вычисляется заново каждый раз при вызове подпрограммы RDKEY. RNDL $004E 99 —— Случайное число (младший байт); вычисляется заново каждый раз при вызове подпрограммы RDKEY. SETINV $FE80 73 Y Устанавливает инверсный режим для подпрограммы COUT (так что сим- волы будут высвечиваться черным по белому, а не белым по черному). SETNRM $FE84 73 Y Выключает инверсный режим для подпрограммы COUT (см. SETINV). SPKR $C030 72 — Адрес звукового устройства (выдаю- щего сигналы для получения музы- кальных фраз). STACK $0100 60 — (Аппаратный) стек; массив из 256 символов* 2). WAIT $FCA8 70’ А Ожидает (26+27&+5£2)/2 мкс; прн входе k в регистре А. (Для вызова любой из этих подпрограмм с именем г и шестнадцатеричным адресом h напишите JRS г и затем где-нибудь в программе г EQU ht чтобы определить под- программу.) *) В упражнениях. — Прим, перев. 2) Видимо, имеются в виду не символы, а байты. — Прим, перев. 384
Таблица П12. Регистры, флажки и их информационная емкость, установка флажков а) Регистры и флажки состояния процессора 6502 и их информационная емкость Наиме- нование Тип Число битов Статья книги Команды, использующие данное средство А Регистр 8 6 Перемещения: LDA, STA, TAX, TAY, TXA, TYA, РНА, PLA; операций: ADC, SBC, AND, ORA, EOR; сравнения: CMP; сдвиг: ASL, LSR, ROL, ROR В Флажок 1 67 Установки: BRK; восстановления: PLP, RTI С Флажок 1 15 Очистки: CLC; установки: SEC; проверки ВСС, BCS; операций ADC, SBC; сдвига: ASL, LSR, ROL, ROR; сравнения: CMP, CPX, CPY; восстановления: PLP, RTI D Флажок 1 65 Очистки: CLD; установки: SED; операций: ADC, SBC; восстановле- ния: PLP, RTI I Флажок 1 68 Очистки: CLI; установки: SEI; опе- раций: BRK и внешние прерывания; восстановления: PLP, RTI Байты 8 6 Операций: INC, DEC; сдвига: ASL, памяти LSR, ROL, ROR; перемещения: STA, STX, STY, использования памя- ти: LDA, LDX, LDY, ADC, SBC, AND, ORA, EOR, BIT N (см. S) Р Регистр 8 67 Сохранения: PHP; восстановления: PLP, RTI (cm. S, V, D, I, B, Z, C) PC Регистр 16 27 Ветвлений: ВСС, BCS, BEQ, BNE, BPL, BMI, BVC, BVS; переходов: JMP, JSR, BRK; возврата: RTI, RTS S Флажок 1 28 Перемещения: LDA, LDX, LDY, TAX, TAY, TXA, TYA, TSX, PLA; опера- ций: ADC, SBC, AND, ORA, EOR, INC, INX, INY, DEC, DEX, DEY; сравнения: CMP, CPX, CPY, BIT; проверки: BPL, BMI; сдвига: ASL, LSR, ROL, ROR; восстановления: PLP, RTI 25-589 385
Продолжение Наиме- нование Тип Число битов Статья книги Команды, использующие данное средство S Регистр 8 60 Вызова: JSR; возврата: RTI, RTS; перемещения: РНА, РНР, PLA, PLP, TXS, TSX Байты стека 8 59 Вызова: JSR; перемещения: РНА, РНР; использования стека: RTI, RTS, PLA, PLP V Флажок 1 56 Очистки: CLV; проверки: BVC, BVS; операций: ADC, SBC, BIT; восста- новления: PLP, RTI X Регистр 8 6, 13 Перемещения: LDX, STX, TAX, TXA, TSX, TXS; операций: INX, DEX; сравнения: CPX; индексирования: ADDR, X, ZPAGE, X, (ZPAGE, X) Y Регистр 8 6, 13 Перемещения: LDY, STY, TAY, TYA; операций: INY; индексирования: ADDR, Y, (ZPAGE), Y, ZPAGE, Y Z Флажок 1 20 Перемещения: LDA, LDX, LDY, TAX, TAY, TXA, TYA, TSX, PLA; операций: ADC, SBC, AND, ORA, EOR, INC, INX, INY, DEC, DEX, DEY; сравнения: CMP, CPX, CPY, BIT; проверки: BEQ, BNE; сдвига: ASL, LSR, ROL, ROR; восстановления: PLP, RTI б) Установка флажков командами C Сложение с переносом: ADC; вычитание с переносом (инвер- сия состояния займа): SBC, СМР, СРХ, CPY; левый бит со- держимого регистра при сдвиге в регистре: ASL, ROL; пра- вый бит содержимого регистра при сдвиге в регистре: LSR, ROR. S 1, если знаковый результат меньше нуля; 0, если знаковый результат больше или равен 0 V Арифметическое переполнение (см. статью 56): ADC, SBC; второй слева бит v: BIT v. Z 1, если результат равен нулю; 0, если результат не равен нулю. Для команды BIT v результатом в этом смысле является само v, а не (А и о). 386
Таблица П13. Команды системы LISA (редактирование, ассемблирование, сохранение файлов и т. д.) Имя команды Статья в книге Описание команд системы АР f 47 Загружает (см. LOAD) программу, файл которой имеет имя f, и присоединяет ее к концу текущей программы в памяти. ASM 46 Ассемблирует текущую программу. (В случае ошибки, после ее фиксирования, введите С и воз- врат каретки для продолжения или А и возврат каретки для прекращения.) BRK 46 Прерывание (передается от системы LISA мони- тору) . ctrl-D с 92 Выполняет команду DOS с. (Ctrl-D EXEC f за- гружает файл f, записанный перед этим коман- дой W f, а не SAVE f). D n 46 Удаляет строку с номером л; перенумеровывает строки файла по порядку. D mt n 46 Удаляет строки с номерами /и, лг+1,..., л; пере- нумеровывает строки файла по порядку. I m 46 Включает последующие строки (заканчиваю- щиеся символами control-E и возврат каретки) перед строкой с номером /и; перенумеровывает строки файла по порядку. L 46 Выводит (обычно высвечивает на экране) всю вашу программу. L m 46 Выводит (обычно высвечивает на экране) строку с номером т. L mt n 46 Выводит (обычно высвечивает на экране) строки с номерами т, лг-f-l, ..., и. LOAD f 47 Загружает (пересылает с диска в память) про- грамму, файл которой имеет имя f. M m 46 Модифицирует (изменяет) строку т (эквива- лентно последовательности команд D т и I т). M m, n 46 Модифицирует (изменяет) строки лг, лг+1, ..., п (эквивалентно последовательности команд L т, п\ D т, л; I лг, л). NEW 92 Дает указание системе LISA начать работу с но- вой программой. SAVE f 46 Сохраняет (пересылает из памяти на диск) вашу программу и дает файлу с программой имя f. W f 92 Подобно SAVE, но файл на диске является тек- стовым файлом, так что его можно считать про- 1 граммой Бейсик (см. ctrl-Dс). (Команды монитора Эпл см. в табл. П14). 25* 387
Таблица П14. Команды монитора (для отладки, проверки наложения и т. д.) Символ команды Формат команды Статья в книге Описание команды монитора Нет хххх 47 Выводит содержимое ячейки хххх в виде двух шестнадца- теричных цифр. Двоеточие хххх: hh 48 Пересылает hh (эти две шест- надцатеричные цифры) в ячей- ку хххх хххх: hh h2 (и т.д.) 48 Пересылает hh в ячейку хххх, h2 в ячейку хххх+1 и т. д. (сколько потребуется). G xxxxG 47 Переходит на выполнение про- граммы по адресу хххх. L xxxxL 47 Выводит листинг с програм- мой на языке ассемблера и на машинном языке (20 команд), начиная с хххх М хххх < у ууу. zzzzM 45 Пересылает уууу в хххх, УУУУ+1 в хххх+1 и т. д.; zzzz — последний пересылае- мый байт. Точка хххх.уууу 47 Выводит на экран содержимое каждый из ячеек хххх, хххх+1, ...., УУУУ в виде двух шестнад- цатеричных цифр. S xxxxS 47 Переходит на выполнение про- граммы по адресу хххх, но вы- полняет (и выводит на экран) только 1 шаг этой программы. xxxxSSSSS (и т. д.) 47 Переходит на выполнение про- граммы по адресу хххх, но выполняет (и выводит на экран) лишь столько шагов, сколько введено букв S. SSSSS (и т. д.) 47 Выполняет (и выводит на экран) столько шагов, сколько введено букв S с того адреса, на котором остановился мони- тор Эпл. Т ххххТ 47 Переходит на выполнение про- граммы по адресу хххх и вы- водит на экран все ее шаги (до прерывания). V xxxx<yyyy-zzzzV 45 Сравнивает уууу с хххх, УУУУ+1 с хххх+\ и т.д.; zzzz — последний сравнивае- мый байт. 388
Таблица П15. Группы кодов операций процессора 6502 —1 Группа 00-0 1 1 1 1 1 Группа 00-0-1 Группа 10-обычная 1 mi 0 0 0 0 m3 1 0 0 1 T m5 —1— aO 1 0 ml= 0 BRK шЗ= 4 BITZ 1 РНР 5 BITQ 2 BPL 9 JMPL 3 CLC 13 JMP(L) 5 р^р Принятые обозначения 6 BMI ml, m2, m3, т4, т5, тб 7 SEC = код мнемоники. 8 RTI al, al = под режима 9 РНА адресации. 10 BVC п си Группа 01 12 RTS i 1 1 1 1 a0=0 1 Z 2 специальная или A .3 Q 5 Z,X или Z,Y 6 специальная 7 QtX или Q, Y Значения aO m5=0 ASL 1,2,3,5,7 1 ROL 1,2,3,5,7 2 LSR 1,2,3,5,7 3 ROR 1,2,3,5,7 13 PLA 14 BVS т4 al 1 0 1 4 51Л 1,3,5 5 LDX 0,1,3,5,7 15 SEI 17 DEY 18 BCC 19 TYA 20 LDY 21 TAY al=0 (Z,X) 1 Z 2 3 Q 4 (Z),Y < 5 Z,X 6 DEC 1,3,5,7 7 INC T,3,5,7 Группа К)-обычная (aO-2 или 6) U. о 4.1 23 CLV 7 Q.X Jf _ тб 1 0 1 0 24 CPY 25 INY 26 BNE 27 CLD 28 CPX *n , 29 INX 30 BEQ 31 SED Группа 00*1 Значения a 1 m4=0 ORA Bee 1 AND Bea 2 EOR Bee 3 ADC Bee 4 STA Хроме 2 5 LDA Bee 6 CMP Bee • 7 SBC Bee m6= 0 ASL 2 ROL 4 LSR 6 ROR 8 TXA 9 TXS 10 TAX 11 TSX 12 DEX 1 l m2 l 1 aO 1 —I 0 0 „ 14 NOP Описания команд Значения aO mZ=0 STY 1,3,5 1 LDY 0,1,3,5,7 2 CPY 0,1,3 3 CPX 0,1,3 см. гпабл. ПЗ Описание режимов адресации см. табл. Пв
Ответы к упражнениям, помеченным звездочкой Замечание: Многие упражнения требуют написания про- грамм, и приведенные здесь ответы к таким упражнениям сле- дует рассматривать лишь как возможные варианты, поскольку очевидно, что любую программу можно написать различными способами. По той же причине здесь не приводятся решения за- дач для выполнения на ЭВМ. 1—1. б) FIFTEEN INCHES1). 1—2. a) MEET ME BEHIND THE FENCE2). в) WILL YOU GO OUT WITH ME?3). 2—1. a) 1100100 в) 10000000 2—2. 6) 140 2—3. I AM LEARNING ABOUT COMPUTERS4). 3—1. б) 11000 3—2. а) 10011 в) 11110 4—1. а) 64 в) 1000 4—2. б) 291 4—3. AFABCAB4DAD 5—1. б) D6AA 5—2. а) 215 в) 41F 6—1. б) Нет (больше 256). 6—2. а) А=1, Х=0. (256 в двоичной форме представляет собой 0000000100000000, и из этих 16 битов первые 8, т. е. 00000001, будут помещены в регистр А, а остальные 8, t. e. 00000000, будут помещены в регистр X.) в) А=39, Х=16 (десятичные). 6—3. 212— 1, или 4095. 7—1. б) 00110110 7—2. а) -24 __________в) -125 Пятнадцать дюймов. — Прим, перев. Встречай меня за околицей. — Прим, перев. 3> Выйдешь погулять со мной? — Прим, перев. 4) Я узнаю про компьютеры. — Прим, перев.
Ответы к упражнениям, помеченным звездочкой 391 8—1. a) LDA ВЗ STA В5 (Здесь, как и в следующих двух упражнениях, мы можем за- менить всюду А на X или Y.) б) ' LDA #!0 STA Z 8—2. б) W=50 и W2=50 (или W=50 и W2=W) 8—3. LDA Р LDY Q STA Q LDA R STY R STA P (Возможно много других решений.) 9—1. б) INC F4 9—2. а) КЗ=К4-1 в) J=S C=S+1 (или C=J+1) 10—1. 080В А9 00 080D 8D DA 08 10—3. М=3 N=3 (или N=M) 11—2. ORG $08D0 LDA #$50 (или LDA #!80) STA C ORG $08FA C DFS !1 END 12—1. 09C0 A9 00 09C2 8D 82 09 09C5 8D 80 09 09C8 8D 81 09 12—3. 0840 A9 00 0842 8D 59 08 0845 A9 64 0847 8D 58 08
392 Ответы к упражнениям, помеченным звездочкой 13—1. б) LDA J STA Т+!6 13—2. a) U(J)=T(J) в) U(M)=U(M)-1 14—1. а) (5А) (СЗ) + (ЗА) (2F) (94) (F2) 14—2. 1) Сложите с и f, чтобы получить i, и зафиксируйте перенос. 2) Сложите b и е и прибавьте перенос (если он был), чтобы получить Л, и зафиксируйте перенос от этой операции. 3) Сложите а и d и перенос, если он был, и получи- те g. 15-1. а) LDA К5 CLC ADC Кб в) STA J LDX J LDA U, X CLC ADC #15 STA Т, X 15—2. 15—3. б) W=N 1.4-32 LDA VI CLC ADC V2 . STA V3 LDA VI4-11 ADC V24-H STA V34-I1 LDA V14-12 ADC V24-12 STA V34-12 16—1. а) б) в) Нет, потому что результат положителен. Нет, потому что результат равен нулю. Да, потому что результат отрицателен. 16—3. а) 1, и флажок переноса будет установлен. 17—1. б) LDA Р или LDA Р CLC SEC
Ответы к упражнениям, помеченным звездочкой 393 ADC #!9 ADC #!9 LDX L LDX L SEC SBC R, X SBC R, X STA S9 17—2. a) H=T(J)—T(3) в) U(I)=160+T(I)-U(I) 18—1. a) LDA I CLC ADC J TAX LDA #!5 LDA N, X STA Pl, X STA S9 в) LDX J или LDX J LDA N, X LDY N, X TAX LDA N, X LDA N, Y STA J 18—2. 6) W=V4-14-U STA J 18—3. a) LDA С или TAY LDY TYA C STA U, Y в) INC V STA U, Y 19—1. б) Эту метку не следует использовать (а в системе LISA 2.5 и нельзя); она содержит 11 символов, а это слишком много. г) Эту метку можно использовать. 19—2. а) LDA T1 ; Заметьте, что первая команда перехода CLC ADC T2 ; на ERROR абсолютно необходима. ; Можно подумать, что это не так, BCS ERROR ; поскольку если Т1+Т2>255, то ADC T3 ; Т1+Т24-ТЗ наверняка >255. ; Однако, если прибавление Т2 BCS ERROR ; устанавливает перенос, прибавление ; ТЗ может и не установить его STA M ; (напр., 200+100+100), ; а результат неверен
394 Ответы к упражнениям, помеченным звездочкой B) LDA К CLC ADC L BCS ERROR TAX LDA R CLC ADC #!4 BCS ERROR STA T, X 20—1. a) LDA В CLC ADC C CMP D BEQ TWENTY в) LDA c SEC SBC T-H6 CMP #13 BEQ TWENTY 20—2. б) IF R— 1 = S THEN 20 20—3. В составе команд процессора 6502 отсутствует коман- да вида CPX T.Y; мы можем сравнивать индексированную пере- менную с использованием индекса только с содержимым реги- стра А, но не X или Y. 21—1. б) FOR J=1 ТО 100 ч IF W=V(J) THEN 80 NEXT J 21—2. a) LDX #!0 LOOP INX TXA STA T, X CPX M; (или CMP M) BNE LOOP
Ответы к упражнениям, помеченным звездочкой 395 в) LDX К INX STX КР1 LDX #!0 LOOP INX DEC Т, X CPX KPI BNE LOOP 22—1. LDY #!0 LDX N LOOP LDA T, X BNE NON INY NON DEX BNE LOOP STY К 22—3. Команду CPY можно опустить, поскольку команда DEY устанавливает и сбрасывает флажок нуля точно при тех же условиях, что и использованная здесь команда CPY. 23—1. LDY #!0 LDX N LOOP LDA Т—!1,Х BNE NON INY NON DEX BNE LOOP STY К 23—2. 6) S=T(I—J—2) 23—3. a) LDX D LDA T+!1,X в) LDX N LDA Т+!10,Х 24—1. б) VELMA DYES HER HAIR» 24—2. a) CE D5 CD C2 C5 D2 AO D4 CF CF AO C2 C9 C7 в) C9 CC CC C5 C7 Cl CC АО C3 CF C4 C5 *> Бельма красит волосы. — Прим, перев.
396 Ответы к упражнениям, помеченным звездочкой 25—1. а) Программа вводит 10 символов и размещает их в массиве REV в обратном порядке (REV содержит последний символ, a REV + !9 — первый). в) Программа вводит 1 символ, выводит его на экран (с помощью RDKEY) и выводит также следующий по алфавиту символ («В» в ответ на «А» и т. д.); если вы вводите символ Z, выводится А. 25—3. Команду LDA *$8D можно поместить перед началом цикла (сразу после JSR), поскольку в течение всего времени выполнения цикла в регистре А хранится $8D. 26—1. б) KPRIME BYT "+" (или KPRIME BYT $АВ) 26—2. FA (СЗ плюс 22 плюс 15, так как "С"=$СЗ— см. табл. П9 — и % 10101 = $15). 27—1. 0840 А2 FF 0842 BD 00 02 0845 D0 19 0847 СА 0848 DO F8 084А 8D 88 08 27—3. FZERO EQU $08F0 INBUF EQU $0200 ORG $08E0 LOOP LDX INX CMP BEQ BNE #$FF INBUF,X LOOP FZERO 28—1. б) IF U(J)> = U.(K) THEN 50 28—2. а) LDA A2 LDX К CMP Т+П.Х BCS FIFTY в) LDX J или LDX J ! LDA U—!1,X LDY U—!1,X TAX LDA #!2 LDA #!2 CMP U—!1,Y j CMP U—!1,X BCC FIFTY BCC FIFTY OZ£ Ь
Ответы к упражнениям, помеченным звездочкой 397 29—1. LDA P CLC ADC Q ТАХ LDA P+ll ADC Q+ll СМР R+ll BNE DECIDE СРХ R DECIDE ВСС LESS 29—3. LDX N LOOP LDA P-11,X СМР Q-11,X BNE DECIDE DEX BNE LOOP DECIDE BCC LESS Заметьте, что если в регистре X оказывается нуль, нам не нуж- но переходить на метку LESS, потому что Р=0; но тогда коман- да BNE LOOP не осуществит переход, так же как и команда ВСС. 30—2. INC Р BNE NEXT INC P+ll BNE NEXT INC P+!2 NEXT (следующая команда) 31—1. a) P=4*Q+R в) B2=8*B1 31—3. Команда ASL X будет обращаться к переменной с именем X, а не к регистру X. 32—1. a) LDA Т+110 STA В LDA Т+111 STA В-+-11 в) LDA К ASL ; Заметьте, что команда CLC в этой ; программе не является абсолютно
398 Ответы к упражнениям, помеченным звездочкой ТАХ ; необходимой, поскольку LDA В ; флажок нуля сбрасывается CLC ; командой ASL (см. конец ADC Т—!6,Х ; статьи 31) и не изменяет STA В ; своего состояния LDA ВЧ-11 ; командами ТАХ ADC Т-!5,Х ; или STA B + I1 ; LDA 32—3. Нам надо умножать J и К на 3, а не на 2, поскольку в 24-разрядной величине 3 байт. Программа выглядит следую- щим образом: LDA J ASL ADC J ТАХ LDA К ASL ADC К TAY LDA Т,Х СМР T.Y BNE DECIDE LDA Т+ !1,Х СМР T+I1.Y BNE DECIDE LDA Т+!2,Х СМР T+!2,Y DECIDE ВСС LESS 33—1. а) Т=6*Т в) F=13*G 33—3. Соответствующей последовательности из 5 команд не существует. Если команда SBC стоит в начале, перед ней на- до написать SEC. В данной же программе команда SEC не нуж- на, потому что состояние флажка переноса после команды BCS известно. 34—1. б) ASL Р ROL Р + 11 ROL Р+12
Ответы к упражнениям, помеченным звездочкой 399 34—2. Потому что флажок переноса содержит только один бит. При сложении длинной последовательности чисел (вроде данной) суммарное значение переноса может быть больше 1 (в данном случае оно равно 2), но вы не можете хранить такое число в 1-разрядном регистре переноса. 35—1. Ошибка заключается в том, что последний сдвигаемый бит не считается. Заметьте, что команда ASL устанавливает флажок нуля в соответствии с содержимым регистра А; состоя- ние флажка переноса не имеет значения. Эту ошибку нельзя исправить, изменив строку инициализации на LDY *11 (потому что, в частности, в регистре А может не быть ни одного установ- ленного бита). А вот приведенная ниже программа будет рабо- тать правильно: LDY #$FF ; Инициализация счетчика ( — 1) СВ1 INY ; Инкремент счетчика CB2 ASL ; Посмотрим на следующий бит BCS CB1 ; Если 1, инкремент счетчика BNE CB2 ; Если есть еще биты, посмотрим ; на следующий 35—3. . LOOP LDX #!8 LDA BITS —!1,Х LSR ROR Q DEX BNE LOOP При последнем выполнении цикла в регистре X будет 1 (не 0; команда BNE анализирует содержимое регистра X после его декремента). Поэтому команда LDA BITS, X неверна, так как в последнем теле цикла она загрузит содержимое ячейки BITS-J-ll, а первый байт BITS будет пропущен. Принципиаль- ное изменение заключается в том, что биты должны вдвигаться в Q слева направо (с помощью команды ROR), а не справа налево, потому что мы проходим массив BITS с конца, а не с начала. 36—2. В цикле находятся команды INX (2 машинных цик- ла); STA Т, X (5 циклов), CPX N (4 цикла) и BNE LOOP (3 цикла, если переход выполняется). В сумме получается 14 машинных циклов, а для N шагов будет 14N —1 машинных цик- лов (поскольку команда BNE в последнем шаге не выполняет переход). К этому мы должны добавить команды LDX ф!0 (2 машинных цикла) и ТХА (2 цикла), что дает в сумме 14N+3 машинных циклов. Число байт составит 2(LDX=!0)4-l(TXA) +
400 Ответы к упражнениям, помеченным звездочкой 1(INX)+3(STA Т, Х)+3(СРХ N)4-2(BNE LOOP)—не за- будьте, что команда BNE содержит 8-разрядный относительный адрес, всего 12 байт. 37—1. Да. Например, машинное время первой ЭВМ может быть более чем в 100 раз дороже машинного времени второй машины (вы можете найти и другие условия). 37—3. Нет; первая из этих программ и быстрее, и короче второй. Заметьте, что данные занимают одно и то же количе- ство байт независимо от того, используется ли последователь- ный или параллельный массив. Последние шесть команд первой программы используют столько же времени и памяти, что и по- следние шесть команд второй программы. Так что высказанное утверждение основано на сравнении оставшихся строк обеих программ. 38—1. $ п х 1010 00 11___ 11110 38—2. а) 10001Ю I 111 б) 1000000 I 1010 в) 11001000 I 1101 111 1010 111 111 1010 110 1100 1010 1101 1111 00 ООО о 11000 1101 10110 1101 10010 1101 39-1. a) S = (P+Q)*(R-1) в) 39—2. б) LDA J SEC SBC #!5 LDX К JSR MULT TXA LDX J STA T--!1,X
Ответы к упражнениям, помеченным звездочкой 401 39—3. б) Первая команда ВСС использует 3 машинных цик- ла в случае перехода и 2 цикла (плюс 2 от команды CLC и 4 от команды ADC, а всего 8 машинных циклов) в противном слу- чае. Первая команда ROR требует 2 циклов; BNE в случае пе- рехода— 3 циклов. В сумме получается от 16 до 21 машинного цикла. Итак, программный цикл требует от 16>|с8—1 до 21 ^<8—1, т. е. от 127 до 167 машинных циклов. в) Пять команд в начале программы требуют в сумме 44-24-4+24-2=14 машинных циклов. Две команды в конце программы требуют 4+6=10 циклов. Прибавление 24 циклов к пределам, найденным в п. б), дает минимум 151 машинный цикл, максимум 191 машинный цикл. 40—1. a) S = (Q-1)/(R+1) в) L = 10*(I-J)/K 40—2. a) LDA J CLC ADC К ТАХ LDA #!0 LDY L JSR DIV STX I в) LDA #!0 LDX L LDY М JSR DIV LDA #!0 LDY N JSR DIV STX W 41—1. а) 51; 101; 201. в) 5; 10; 20. 41—2. б) LDA J ; (или LDA #1200 LDX 4+1200 ; и LDX J) JSR MULT JSR DECOZ 41—3. Не был загружен регистр А (перед вызовом подпро- граммы DECOZ он должен содержать 0). 26—589
404 Ответы к упражнениям, помеченным звездочкой 48—2. Потому что возникает трудность с продолжением про- граммы после останова по адресу а. Если вы продолжите со следующей за а команды, команда по адресу а так и не будет выполнена. Если вы продолжите с точки а, то по этому адресу расположена команда BRK, и вы получите новый останов, не выполнив перед ним ни одной команды. Если вы удалите точку останова по адресу а и продолжите с этого адреса, ваша про- грамма будет выполняться правильно, но в следующем шаге цикла уже не остановится. 49—1. Если результат равен п, тогда, очевидно, х!2п=у, откуда 2п=х!у. Определение, данное в упражнении, говорит нам, что n=log2(x/z/), или, что то же самое, log2x—log2t/. 49—3. а) Число шагов, требующееся для перехода от таб- лицы размера п к таблице размера 2, составляет k, причем гг/2*=2, так что &=log2(n/2), как и в упр. 1. Отсюда k= =log2n—1, но мы должны добавить последние 2 шага, так что получается формула l+log2n. Если п не является степенью двух, тогда log2n не целое число, и надо взять ближайшее боль- шее целое (поскольку нельзя выполнить долю шага). б) Потому что цикл в этой программе выполняется небольшое число раз. (Даже для таблицы размером 1000 требу- ется только И шагов, так как 210=1024; а число И невелико» и можно использовать обычный метод отладки с точками оста- нова.) 50—2. Измененная программа такова: LDA Р CLC ADC Q ВСС ВЕТА JMP PATCH1 BETA. STA Q Заметьте, что 3 байта команды INC Q-J-11 были заменены 3 байтами команды JMP PATCH 1. Текст заплаты: PATCH 1 INC Q + !l BNE PATCH2 INC Q + 12 PATCH2 JMP BETA Мы использовали команду BNE PATCH2, а не просто BNE BETA, потому что заплата может быть удалена от исходной последовательности команд гораздо больше, чем на 126 байтов (см. статью 27). z
Ответы к упражнениям, помеченным звездочкой 405 50—3. Измененная программа такова: LDX LDY #!0 #!0 GAMMA JMP PATCHI BYT 10 BYT !0 GAMMA1 BCC CLC DELTA ADC DELTA NOP TAY INX CPX #!8 BNE GAMMA STY BIN Заметьте, что 1-байтовая команда команду^ASL (с меткой DELTA). Текст заплаты: BITS -11,X NOP заменила 1-байтовую GAMMA 1 байта. Мы не можем исполь- РАТСН1 LDA LSR TYA ASL JMP Команда JMP PATCHI требует 3 зовать ее вместо TYA (1 байт) или LSR и TYA (в сумме 2 бай- та); мы вынуждены изъять команды LDA, LSR и TYA (в сумме 5 байтов). Две псевдокоманды BYT !0 уравнивают число изъ- ятых и вставленных байтов (вставлено 5 байтов: 3 занимает команда JMP PATCH 1, и 2 — псевдокоманды BYT). 51—1. б) 51—2. а) в) FOR РОКЕ NEXT POKE 32771,1 POKE 32772,J CALL 32768 M=PEEK(32772) FOR J=1 TO 100 POKE 32999+J,T (J) NEXT J J=1 TO 100 33000+J,T (J) J FOR J=1 ТО 101 POKE 32999+J,T(J) NEXT J
402 Ответы к упражнениям, помеченным звездочкой 42—2. Две команды END (первую надо опустить); после ко- манды INX нет команды RTS (подпрограмма должна заканчи- ваться командой RTS); пропущено определение I DFS !1 (либо могла бы быть секция данных). 43—1. Команде LDA назначены 3 байта; следовало назна- чить 2, поскольку загружается константа. Команде BNE назна- чены 3 байта; следовало назначить 2, потому что относительный адрес занимает только 1 байт. Для псевдокоманды DFS отведен 1 байт; это неверно, так как мы не знаем, что находится в N. 43—3. Машинная команда в строке с меткой STAR должна быть AD 08 09, а не AD 09 08 (байты должны быть перестав- лены; заметьте, что машинная команда в строке с меткой LOOP содержит переставленные байты). Команда BEQ DONE имеет машинную форму F0 03 (а не F0 05). Здесь забыли прибавить 2; действительно, 0911+5=0916, но если 0911+2+2=0916, то 2=3. 44—2. Ошибка заключается в том, что метка LOOP должна относиться к команде LDA Т—!1, X, а не к команде LDX. Если оставить программу, как она написана, цикл будет бесконеч- ным, так как вы всегда возвращаетесь на загрузку регистра X начальным значением. Ошибка становится очевидной при руч- ной прогонке, когда команда с меткой LOOP выполняется во второй раз, и таблица выглядит следующим образом (в ней мог быть также столбец для флажка переноса): A SUM X Д' -О' Л 15 15 4 Заметьте, что в столбце для А число 15 появляется дважды, по- тому что загрузка регистра А значением Т(4) во второй раз да- ет «новое значение» — хотя в данном случае это значение сов- падает с предыдущим в этой же точке. 45—1. После команды LDA I будет «выполняться» строка с данными с меткой I; после команды BNE MIX2 может «вы- полняться» строка с данными с меткой J (если 1=0). Заметьте, что после команды JMP MIX3 могут стоять данные (с меткой К), поскольку команда JMP всегда «обходит» эти данные. Точ- но так же после команды LDA J не будет никаких неприятно- стей с «выполнением» данных, поскольку строка COUT EQU $FDED не определяет данные (посмотрите на строки машинно- го языка, и вы увидите, что после команды LDA J по адресу 0В07 сразу следует команда JMP MIX3 по адресу 0B0A). 45—3. После первого вызова подпрограммы START, при первом выполнении команды STA, в регистре X будет 100, или
Ответы к упражнениям, помеченным звездочкой 403 .-шестнадцатеричное 64. Поскольку массив Т начинается по адре- су 1870, процессор сложит 0870 и 64 и получит 08D4. Это, од- нако, адрес кода операции LDX #!100. Искажение содержимо- го этой ячейки не повлияет на первое выполнение подпрограм- мы START, потому что к моменту наложения команда LDX #1100 уже будет выполнена. Ошибка выявится при втором вы- зове подпрограммы START, когда команда LDX ф=!100 не бу- дет выполнена, потому что ячейки 08D4 и 08D5 уже не содер- жат А264. Вместо этого в них теперь записано Е964 (из-за на- ложения на первую ячейку); а Е964 — это машинный код ко- манды SBC #1100 (см. табл. П4). В результате процессор вы- полнит команду SBC #1100 (которая затрагивает только ре- гистр А), а за ней команду LDA #1233 (которая уничтожит ре- зультат команды SBC, в чем бы он ни заключался). Поэтому реальное отличие будет в том, что регистр X окажется незагру- женным; подпрограмма START заполнит числом 233 элементы массива от Т-|-!1 до T-j-In, где п — содержимое регистра X к моменту вызова подпрограммы. Если это содержимое было больше 100, программа наложит число 233 на саму себя, и вы- полнит команду BNE с относительным адресом 233’> (что при- ведет к переходу на 21 байт назад). В результате процессор обратится к массиву Т и начнет интерпретировать его содержи- мое, как коды команд. 46—1. б) 1 LDA Р 46—2. а) I 1 LDA М STA N в) М 4 STA Т 47—1. 083А С8 INY A=8D Х=32 Y=65 Р=30 S=F8 ^Самое важное здесь — запись Y=65, 65 — это шестнадцате- ричное значение содержимого регистра Y после его инкремента командой INY.) 47—3. Ошибка заключается в том, что метка LOOP1 должна указывать на команду ASL, а не DEX. Результаты пошагового выполнения показывают, что содержимое регистра А не изменя- -ется, как это следовало бы ожидать (или, другими словами, не выполняется команда ASL), между первым и вторым выполне- нием команды ВСС. 48—1. б) 0А03: С8 (С8 — код операции INY) *’ Для этого надо, чтобы исходное содержимое регистра X превышало 109 (десятичное). — Прим, перев. 26*
406 Ответы к упражнениям, помеченным звездочкой 52-1. а) 20 (X) 100000 35 — (AND) 00110101 00100000 - Ответ 20 6) 55 -> 01010101 АА (AND) 10101010 00000000 Ответ -> 00 52—2. б) 70 52—3. LDA L5 LDA L5 ROL AND #% 11100000 ROL ASL ROL ROL ROL ROL AND ф% 00000111 ROL Прием заключается в том, что мы выполняем команду ROL че- тыре раза, а не три, потому что ROL осуществляет циклический сдвиг через разряд переноса. 53—1. 6) F6 -> C7 -* (ORA) 11110110 11000111 11110111 - Ответ » F7 53—2. a) 3F в) 95 54—1. Cl) 20 00100000 35 -> 00110101 Ответ (EOR) 00010101 -> 15 в) 55 • 01010101 AA • 10101010 Ответ (EOR) 11111111 FF 54—2. 6) 82 54—3. LDA p EOR #% 10000000 STA TEMP LDA Q EOR #% 10000000
Ответы к упражнениям, помеченным, звездочкой 407 СМР TEMP BCS LEQ 55—2. Команды BIT Т— II, X не существует (вспомните, что команду BIT нельзя использовать с индексацией). 56—1. а) 3 байта. Программа из статьи 54 занимает 18 байт: Команды LDA EOR STA LDA EOR СМР ВСС Сумма Байты 3233232 18 В то же время программа из этой статьи занимает 15 байтов: Команды LDA SEC SBC BVS BPL BMI BPL Сумма Байты 3 1 3 2 2 2 2 15 в) 6 машинных циклов (минимум) или 7 циклов (максимум). Программа из статьи 54 требует 23 машинных цикла, если происходит переход на метку LESS: Команды LDA EOR STA LDA EOR СМР ВСС Сумма Байты 4244243 23 Заметьте, что в этом случае команда ВСС выполняет переход и поэтому требует 3 цикла. Первые 3 команды программы этой статьи требуют 10 циклов (LDA — 4 цикла, SEC — 2 цикла, SBC — 4 цикла). На метку LESS можно перейти двумя путями. В случае перехода по команде BVS (3 цикла) на метку OVSET, где команда BPL также выполняет переход (3 цикла), требует- ся 6 циклов. Если BVS не выполняет переход (2 цикла) и BPL тоже не выполняет переход (2 цикла), то команда BMI обяза- тельно выполняет переход (3 цикла), и в сумме получается 7 машинных циклов. Общий итог—16 или 17 машинных циклов. 56—2. а) LDA P ; Пятая и шестая SEC ; команды SBC Q ; могли бы быть: BVC OVCLR ; (1) BPL LESS, BMI GEQ BMI GEQ ; (2) BMI GEQ, BVS LESS BPL LESS OVCLR BMI LESS ; (3) BPL LESS, BVS GEQ GEQ (следующая команда) б) Требования к памяти не изменяются. 57—2. Потому что каждой команде RTS должны предшест- вовать команды сохранения регистров А, X и Y; в этом случае экономнее с точки зрения расходования памяти иметь только
403 Ответы к упражнениям, помеченным звездочкой одну последовательность этих команд (за которой стоит коман- да RTS) и осуществлять переход на эту последовательность, когда нужно выйти из подпрограммы. Так, подпрограмму а можно заменить подпрограммой б: а) 6) SUBR STA SAVEA SUBR STA SAVEA STX SAVEX STX .SAVEX STY SAVEY STY SAVEY (Следующие команды) (Следующие команды) ; Первый возврат LDA SAVEA LDX SAVEX LDY SAVEY RTS ; Второй возврат LDA SAVEA LDX SAVEX LDY SAVEY RTS ; Первый возврат JMP SUBREX ; Заметьте, что ; мы экономим ; 3 команды (7 байт) ; Второй возврат JMP SUBREX ; Здесь мы экономим ; еще 3 команды ; (еще 7 байт) ; Последний возврат ; Последний возврат LDA SAVEA SUBREX LDA SAVEA LDX SAVEX LDX SAVEX LDY SAVEY LDY SAVEY RTS RTS 57—3. a) f(Q) является абсолютным значением Q. 58—2. . ASL TAX LDA JTABLE-!2,X STA IA LDA JTABLE-!1,X * STA IA + !1 -JMP (IA)
__________Ответы к упражнениям, помеченным звездочкой_409 59—1. IF Х=0 THEN 799 Х = Х-1 А=Н(Х) Эту проверку надо выполнить перед двумя другими командами. В противном случае, если первоначально в регистре X был О, предложение А=Н(Х) обратится к элементу Н(—1), который не существует. 59—3. Х=Х+1 А=Н(Х) Н(Х)=А Х=Х— 1 Проталкивание А Выталкивание А 60—1. б) Указатель стека теперь содержит F6 (6). (Коман- да RTS выталкивает 2 байта; каждый раз, когда выталкивается байт, содержимое указателя стека увеличивается на 1. Если в нем было F4, теперь будет F4-J-2, т. е. F6.) 60—2. а) 2. б) 2. 61—1. K=T(I-J)+I 61—3. Замена команд JSR KOUT и RTS командой JMP KOUT. 62.2. а) Перед выполнением команды RTS из стека следует вытолкнуть 2 байта (1 адрес возврата). 62—3. а) Потому что флажок переноса в этой точке должен быть сброшен, иначе тремя командами раньше мы перешли бы на метку DECIB. в) Потому что мы уже выполнили тремя команда- ми ранее команду ВСС, и перехода не было, так что флажок пе- реноса наверняка установлен. 63—1. б) Флажок переноса должен быть сброшен; в про- тивном случае двумя командами ранее был бы выполнен пере- ход по условию установленного флажка переноса. 63—2. а) Справедливо. в) Ложно; это выполняет команда CPY#N2 (CPY N2 сравнивает содержимое регистра Y с содержимым ячейки с ад- ресом 2). 64—2. а) 129 (потому что 128>|<2 — 2 байта для адреса воз- врата— равно 156, и самый низкий уровень плюс 128 уровней дает 129). б) 52 (потому что 51^<5 — 5 байт, включая два для адреса возврата и по одному для регистров А, X и Y — равно 255. Заметьте, что на самом низком уровне в стеке не запоми- нается адрес возврата, в то время как на самом верхнем уровне не сохраняются регистры А, X и Y). 64—3. Потому что подпрограмма Р, находящаяся на уров- не п, может вызвать подпрограмму только с уровня, меньшего
410 Ответы к упражнениям, помеченным звездочкой п. Если подпрограмма Р вызывает саму себя, это ограничение, очевидно, нарушается, поскольку она вызывает подпрограмму (конкретно саму Р), уровень которой не ниже п (так как он ра- вен п). 65—1. б) 53, флажок переноса установлен; ED, флажок пе- реноса сброшен. 65—2. а) 12, флажок переноса установле'н; 18, флажок пе- реноса установлен. в) 33, флажок переноса сброшен; 93, флажок перено- са сброшен. 66—1. LDX К ; Длину строки в регистр X ТХА ; Разделить ее на 2, получив LSR ; длину упакованной строки, TAY ; и переслать ее в регистр CONVP LDA В—|2,X ; Получить символ строки ASL ; Сдвинуть его на 4 бита ASL ; влево, переслав его правые ASL ; 4 бита в левые четыре ASL ; разряда упакованной строки EOR B—]1,X ; Следующий символ в правые 4 бита EOR #SBO ; (без В в левой половине) STA N-ll.Y ; Записать I символ упакованной DEX ; строки, затем уменьшить DEX ; индекс строки на 2 и DEY ; индекс упакованной строки на I BNE CONVP ; Если не 0, получить следующий символ 66—3. LDX J ; Должны складывать справа налево CLC ; с первоначально сброшенным ; переносом ADDP LDA Nl —!1»X ; Загрузить байт одной строки ADC N2—!1,X ; и сложить с предыдущим ; переносом STA N3-!1,X ; для получения байта ; результата DEX ; Сдвинуть справа налево BNE ADDP ; Если не конец, повторить 67—1. б) BNE ALPHA (флажок Z сброшен, если результат ве нуль).
Ответы к упражнениям, помеченным звездочкой 411 67—2. a) CLV б) CLC 68—1. Адрес возврата 0834, он проталкивается (08 в ячейку 01F3, поскольку указатель стека содержит F3, а 34 в ячейку 01F2); затем проталкивается содержимое регистра Р, так что 31 записывается в ячейку 01F1. В результате новое содержи- мое стека таково: Адрес Содержимое 01F0 А7 01F1 31 01F2 34 01F3 08 01F4 В1 01F5 ЗС 01F6 08 01F7 9Е Регистр S теперь содержит F0 (=F3 —3) Регистр Р сначала со- держал $31, или двоичное 00110001; но флажок I (третий раз- ряд справа) теперь установлен, так как вызов подпрограммы прерывания выключает систему прерывания (1 = 1). Поэтому регистр Р теперь содержит двоичное 00110101, или шестнадца- теричное 35. 68—3. Потому что наша 3-байтовая команда могла быть командой перехода JMP (или JSR) по некоторому адресу, и тогда именно этот адрес станет адресом возврата из прерыва- НИЯ. 69—1. GETLN LDX #!0 69—2. IWAIT LDA INSTAT AND # % 00000001 BEQ IWAIT LDA INDATA STA $ 0200.X INX CMP #$8D BNE IWAIT RTS IWAIT LDA INSTAT LSR (или ROR) ВСС IWAIT LDA INDATA
412 Ответы к упражнениям, помеченным звездочкой 70—2. 135 204 616 машинных циклов, или немного больше 2 мин. Прием заключается в том, что после декремента нуля получается не нуль (конкретно, 255); поэтому начальное значе- ние счетчика цикла, равное 0, приводит к выполнению 256 ша- гов. Приведенное значение получено при подстановке вместо L, М и N числа 256. 71—1. Программа ICHAR должна проверять, не пуста ли очередь. Если это так, она вызывает программу POLL и снова возвращается на строки проверки, не пуста ли очередь. В конце концов в очереди появится один символ (занесенный туда про- граммой QIN, вызванной из программы POLL), и тогда про- грамма ICHAR продолжит свое выполнение. Программа QOUT в свою очередь должна проверять, не пуста ли выходная оче- редь. Если очередь пуста, программе нечего выводить, и она просто завершается. 71—3. Сразу после заполнения элемента Q(REAR), следу- ет выполнить инкремент REAR и проверить, не имеет ли место равенство REAR=m. В перевернутой очереди надо выполнить декремент REAR и проверить, не имеет ли место равенство REAR=0, что немного быстрее. 72—2. Мы имеем 1 023 000/512=1998 (приблизительно). Вы- читание, как и раньше, 7 дает 1991; разделив на 10, получим 199 и 1 в остатке. Если, как и раньше, вычесть 2 из-за команды LDX и прибавить 1 из-за последней команды BNE, получится в остатке 0. Поэтому мы можем удалить все команды в конце цикла (BEQ и две команды NOP), а вместо LDX*!251 напи- сать LDX #1215. 73—1. a) BLK "К9" 73—2. б) STR "YES". 73—3. HEX 0А5634 74—1. а) 5 байт, по одному для строк 1, 3, 8, 10 и 13. За- метьте, что строка 10 всегда выполняется 8 раз, по разу в каж- дом шаге цикла; в то же время строка 8 может выполняться лю- бое число раз от 0 до 8, потому что на строке 6 переход может выполняться, а может и не выполняться. В результате эконо- мится от 8 до 16 машинных циклов. В строках 1, 3 и 13 эконо- мится по одному машинному циклу, поэтому общая экономия составит от 11 (минимум) до 19 (максимум) циклов. б) 9 байт, по одному для строк 1, 2, 4, 6, 9, 12, 14, 17 и 18. Заметьте, что строка 6 всегда выполняется 8 раз, по разу в каждом шаге цикла. Если на строке 8 выполняется переход, мы выполняем строку 12 и, возможно, 14, но не 9; если на стро- ке 8 переход не выполняется, мы выполняем строку 9, но не 12 или 14. В любом случае мы экономим от 8 до 16 машинных цик- лов, плюс 8 циклов на строке 6; в строках 1, 2, 4, 17 и 18 эко-
Ответы к упражнениям, помеченным звездочкой 413 номится еще по одному циклу. В сумме получается от 21 (мини- мум) до 29 (максимум) машинных циклов. 75—2. EXCH LDA (0,Х) РНА LDA (2,Х) STA (0.Х) PLA STA (2,Х) RTS 76—1. FF. (Поскольку адрес, хранящийся в ячейках Q и Q+! 1 с переставленными байтами, выглядит как 0809, чтобы по- лучить из него 0908, надо прибавить FF.) 76—3. Потому что последняя команда LDA (PTR), Y долж- на использовать начальное значение PTR; но это значение бы- ло изменено командой STA. 77—1. б) T(J4-1)=T(J). 77—2. Удаленная команда LDY #10 занимает 2 байта и 2 машинных цикла. Команда STA ZP, требующая 2 байта и 3 цикла (поскольку ZP находится на нулевой странице), заменя- ется командой TAY (1 байт и 2 цикла). Таким образом, в сум- ме экономится 3 байта и 3 машинных цикла. 78—1. LDA #T или LDA #T CLC CLC ADC J ADC J STA ALPHA+H TAY LDA /Т LDA /Т ADC J + ll ADC J + !l STA ALPHA+12 STA ALPHA+12 ALPHA LDA MODIFY ALPHA LDA MODIFY,Y STA W STA W (во второй из этих программ младший байт MODIFY должен содержать 0). 78—3. Второй вариант, очевидно, и быстрее, и короче пер- вого, однако в нем используется регистр Y, который в первом варианте свободен. 79—2. ARRAY0 STA STX LDA ALPHA+ 12 ALPHA+11 #!0
414 Ответы к упражнениям, помеченным звездочкой ALPHA STA MODIFY,Y DEY BNE ALPHA RTS 80—1. a) CMP J изменяется на LI CMP 41=10; CMP J-f-11 изменяется на L2 CMP #10; JUPPER определяется с помощью JUPPER EQU L24-I1, a STA J+ll изменяется на STA JUPPER; JLOWER" определяется с помощью JLOWER EQU LI+11, a STX J изменяется на STX JLOWgR. б) Экономится 4 байта, два на самой переменной J и по одному на командах СМР J и СМР В каждом шаге программного цикла экономится 2 машинных цикла при выпол- нении команды СМР J-H1 и еще 2 при выполнении СМР J-f-H,. если не происходит перехода по команде BNE. Суммарная эко- номия получается от 200 до 400 машинных циклов. 80—2. a) DIV2 изменяется на DIV2 СМР #!0; LDX DDATA2 изменяется на DIV5 LDX =Ц=!0; DDATA1 определяется с помо- щью DDATA1 EQU DIV2+I1; DDATA2 определяется с помо- щью DDATA2 EQU DIV5+I1. 80—3. В программе выходного преобразования DEC0—это байт с меткой DECO9. Обращение к этому байту осуществляет- ся в программе только командами STA, INC и DEC, и ни одна из них не может использоваться с непосредственной адресацией. 81—1. Мы предполагали, что все 5 значений К, от 1 до 5, появляются с одинаковой частотой. Если это не так, если, на- пример, при выполнении этой секции программы К=1 встреча- ется чаще, чем К=#1, то первый вариант оказывается заметно- быстрее. На практике такая ситуация весьма вероятна; в самом деле, крайне редко бывает, что различные возможные значения одной переменной появляются с одинаковой частотой1). 81—3. а) Программа сдвигает Q вправо на 7—X позиций. в) 28, 27, 25, 23, 21, 19, 17 или 15 машинных циклов. Сюда входят 12 циклов на выполнение команд STX, LDA и STA; 3 цикла на команду BNE; и по 2 цикла на каждую команду LSR. При отсутствии перехода по BNE потребуется на 1 цикл меньше. 82—2. Изменить /SPACE на /SPACE —11; изменить 4f=SPACE на #SPACE —11; удалить ADC #11; заменить 5 команд, начи- ная с команды LDY ф!0 следующими: !> Не следует упускать из виду, что в другом крайнем случае, когда К=5 встречается чаще чем К¥=5, первый вариант программы окажется за- метно медленнее второго. Конечно, если это заранее известно, можно пере- ставить строки программы. — Прим, перев.
Ответы к упражнениям, помеченным звездочкой 415 LDY LB,X ; Получить длину этой строки CPY NAME ; Если не равно длине NAME BNE PROC ; строки не могут совпадать 83—1. Изменить команду INY на следующее: INC HST2+I1 INC HST3+11 INC HST4+11 BNE HST2 INC HST2+I2 INC HST3 + I2 INC HST4+I2 Здесь HST2 — метка команды ORA; HST3 — метка первой ко- манды STA; HST4 — метка второй команды STA. 83—3. PRNTAX JSR PRBYT ТХА JMP PRBYT Команда JMP PRBYT эквивалентна последовательности команд JSR PRBYT и RTS (об этом упоминалось в конце статьи 61). 84—2. Предположим, что выполняется проверка условия T(J)<T(J+1). Тогда, если T(J)=T(J4-1), мы переставим эле- менты и установим флажок. Это означает, что требуется еще проход. Но выполняя следующий проход, мы сделаем то же са- мое, снова установим флажок и получим бесконечный цикл. 85—3. а) Да. Меньшая длина из двух длин L1 и L2 — это то же самое, что меньшая длина из двух длин L2 и L1. б) Проблема заключается в следующем. Сохраненный фла- жок переноса говорит нам о том, имеет ли место условие L2<L1 или условие L2^,LI. После предлагаемой замены фла- жок переноса будет фиксировать условия L2^L1 или L2>L1, а это не то же самое. Здесь важно различать все три условия; если L2<L1, мы должны перейти на метку ЕХСН, в противном случае мы должны перейти на метку GAMMA. 86—1. Нет. Конечно, команда STX FIRST записывает содер- жимое регистра X в ячейку FIRST, так что после выполнения этой команды загрузка регистра А из ячейки FIRST приведет к тому же результату, что и команда ТХА. Однако на метку L3 можно попасть с помощью команды BCS; а перед этой ко- мандой содержимое регистра X записывается в ячейку LAST, а не FIRST. 86—3. Да, можно. Когда выполняется команда LDX FIRST, с ячейках FIRST и LAST должно быть одно и то же; в то же
416 Ответы к упражнениям, помеченным звездочкой время незадолго до команды LDX FIRST мы записали содер- жимое регистра X либо в ячейку FIRST (командой STX FIRST), либо в ячейку LAST (командой STX LAST) и после этого не изменяли содержимое регистра X. 87—1. а) U=T(8,4) 87—2. 87—3. а) б) LDX J LDA TENS-11,Х CLC ADC I ТАХ DEC Т-111,X Содержимое регистра X следует увеличить на 10. 88—2. 89—1. а) LDA U+11 SEC SBC V+!l BEQ C2 BVS OVSET BPL GEQ BMI LESS OVSET BPL LESS BMI GEQ C2 LDA U CMP V BCC LESS GEQ (следующая команда) LDY #!50 LDY #!51 LOOP LDA T+11,Y LOOP LDA T,Y STA T,Y STA T— !1,Y INY INY CPY #1200 CPY 4|=!201 BNE LOOP BNE LOOP в) 2 байта (на команде CPY *5200) и 300 машинных цик- лов (так как команда CPY 4М200, требуя 2 машинных цикла, выполняется в программе п. а) 150 раз, и все это удаляется из программы). 89—3. Нет. Счетчик цикла N является здесь переменной, поэтому пользоваться отрицательным индексированием нельзя. 90—1. 13 байтов (3 для команды STA, 2 для LDX, 1 для
Ответы к упражнениям, помеченным звездочкой 417 LSR, 3 для EOR, 1 для DEX, 2 для BNE и плюс байт данных В) и 82 машинных цикла (4 для выполнения команды STA, 2 для LDX и 76 для выполнения программного цикла, требую- щего 11 машинных циклов—2 для LSR, 4 для EOR, 2 для DEX и 3 для BNE — выполняемых 7 раз, но на один машинный цикл меньше в последнем шаге, когда команда BNE не осуществляет переход). б) В (к+1)-м разряде слева в регистре А для к-го шага цикла. Так, после выполнения первого шага частичный резуль- тат хранится во втором разряде слева, после выполнения второ- го шага — в третьем разряде слева л т. д. 91—1. DOSCOM STA ZP+!1 ; Адрес сообщения в регистрах STX ZP А и X, запись его в ZP и ZP+J1 LDY 4HFF Начальное значение LOOP INY в регистре Y равно — 1 К следующему символу LDA (ZP),Y сообщения Получить этот символ CMP #CRET Если это возврат каретки, BEQ DONE вывести его и закончить JSR COUT В противном случае JMP LOOP вывести его и получить следующий символ DONE JMP COUT (то же, что и JSR COUT 91—2. DOSCOM STA LOOP1 + I2 и RTS) ; Адрес сообщения STX LOOP1 + I1 ; в регистрах ; А и X, ; модифицировать адрес в LDX #SFF ; строке с меткой LOOP1 ; Начальное значение LOOP INX ; в регистре ; X равно —1 ; К следующему символу ; сообщения 27—599
418 Ответы к упражнениям, помеченным звездочкой LOOP1 LDA MODIFY,X ; Получить этот символ CMP #CRET ; Если это возврат каретки, BEQ DONE ; вывести его и закончить JSR COUT ; В противном случае ; вывести ; его JMP LOOP ; (то же, что и JSR COUT DONE JSR COUT ; и RTS) 92—1. Потому что команда LOAD f загружает двоичные, а не текстовые файлы (о чем можно догадаться, вспомнив, что команда SAVE f, как отмечалось, сохраняет двоичные файлы). 92—3. . LDA Р1 STA Р2 NLS LDA Р5 STA Р6 BRK END 93—1. p=a+5*(N—₽). 93—3. а) Оно должно увеличиться на 5. 94—2. Нет, потому что предложение может, например, быть таким: А=В>^С1. 95—1. Да. Как отмечалось в статье 86, поиск в таблице (весьма обычная для ассемблера операция) выполняется гораз- до быстрее, если таблица упорядочена. 95—3. Да, если у вас есть ассемблер, который будет транс- лировать программы на языке ассемблера, преобразуя их в по- следовательность машинных кодов. Вы получите двухступен- чатую систему, выполняющую функции компилятора (который, как указывалось в тексте, действительно представляет альтер- нативу использованию интерпретатора). 96—1. a) IF С THEN GO ТО /и; S; GO ТО п; пг: Q1; IF NOT D THEN GO TO m; n: 96—2. a) IF Cl THEN SI ELSE WHILE C2 DO S2
Ответы к упражнениям, помеченным звездочкой 419» 96—3. а) JMP L3 LDA CLC ADC STA L4 LDA СМР ВСС L4 К (эту команду можно опустить). #!5 К К L L3 97—1. 97—2. 98—1. 98—2. 98—3. б) 3/5 а) 00.110011 в) 11.001001 а) 40600000 в) 5D200000 б) 101 1F000400; нормализованная форма — 1С400000. 99—2. Произведение шестнадцатеричных мантисс .2 и .3 равно .06; сумма несмещенных порядков, 1 и 1, равна 2, что со- ответствует смещенному порядку 42. Ненормированный резуль- тат равен 42060000, что в нормализованной форме составляет 41600000. Это можно проверить, заметив, что (41200000) >|с (41300000) = (41600000) соответствует десятичному (или шест- надцатеричному) умножению 2>|<3=6. 100—1. Нет, потому что тип каждой переменной фиксирован в каждом конкретном прогоне программы (так как очевидно, нет способа изменить в ходе выполнения программы имя пере- менной) . 100—3. Если TJ=TK=0, складываем J и К как 8-разряд- ные величины и записываем результат в J. Если TJ=TK=1, складываем J и К как 16-разрядные величины и записываем результат в J и J-J-Il. Если TJ=1, а ТК=0, прибавляем 16-раз- рядную величину J к 8-разрядной величине К (см. статью 19) и записываем результат в J и J-f-11. Наиболее сложный случай соответствует комбинации TJ=0 и ТК=1. Как и раньше, надо сложить 8-разрядную и 16-р азрядную величины, но результат должен поступить назад в 8-разрядную величину J. Существуют два способа решения этой задачи. Первый — перейти на строку выхода по ошибке, если результат не помещается в 8 разрядов (как мы и делали при делении). Второй — записать только пра- вые 8 битов результата (так процессор 6502 выполняет опера- ции сложения и вычитания). Что недопустимо, так это рассмат- ривать результат как 16-разрядную величину, потому что в этом случае мы должны иметь TJ=1 (для определения J как 16-разрядной величины), а по условию задачи операция сложе- ния не изменяет тип величины J.
Литература Если вы захотите подробнее познакомиться с языками ассемблеров раз. личных ЭВМ, вы можете прочитать следующие -книги: 1. Birngaum, I., Assembly Language Programming for the BBC Microcompu- ter, Macmilan, London, 1982. 2. Camp, R. C., Smay, T. A., Triska, C. J. Microcomputer Systems Principles Featuring the 65021KIM, Matrix Publishers, Portland, Ore., 1978. 3. Cohn, D. L., and Melsa, J. L., A step By Step Introduction To 8080 Micro- processor Systems, Dilithium Press, Portland, Ore., 1977. 4. Daley, H. 0. Fundamentals of Microprocessors, Holt, Rinehart, and Wins- ton, New York, 1983. 5. Dow, J. T., Dow, D. B., TI Home Computer Assembly Language Pri- mer, John T. Dow, Pittsburgh, Pa. 15217, 1984. 6. Findley, R., 6502 Software Gourmet Guide and Cookbook, Scelbi Publica- tions, Elmwood, Conn., 1979. 7. French, D., Inside the Commodore 64, French Silk, P. 0. Box 207, Cannon Falls, Minn. 55009, 1983. 8. Inman, D., and Inman, K., The ATARI Assembler, Reston Publishing Co., Reston, Va, 1981. 9. Kane, G., Hawkins, D., and Leventhal, L., 68000 Assembly Language Prog- ramming, Osborn/McGraw-Hill, Berkley, Calif., 1981. 10. Kudlick, M. D., Assembly Language Programming for the IBM Systems 360 and 370, Wm. C. Brown Co., Dubuque, Iowa, 1980. 11. Lemone, K. A., and Kaliski, M. E., Assembly Language Programming For The VAX-П, Little, Brown & Co., Boston, 1983. 12. Leventhal L. 6502 Assembly Language Programming, Osborn/McGraw-Hill, Berkley, Calif., 1979. 13. Leventhal, L. 6809 Assembly Language Programming, Osborne/McGraw- Hill, Berkley, Calif., 1981. 14. Leventhal, L., 8080A/8085 Assembly Language Programming, Osborne, Berkley, Calif., 1978. 15. Leventhal, L., and Saville, W., 6502 Assembly Language Subroutines, Os- borne/McGraw-Hill, Berkley, Calif., 1982. 16. Mateosian, R., Programming the Z8000, Sybex, Berkley, Calif., 1980. 17. Morse, S. P., The 8086 Primer, Hayden Book Co., Rochelle Park, New Jer- sey, 1978. 18. Mottola, R., Assembly Language Programming for the APPLE II, Osbor- ne/McGraw-Hill, Berkley, Calif., 1982. 19. Osborne, A., An Introduction To Microcomputers, Osborne, Berkley, Calif., 1976. 20. Rector, R., Alexy G., The 8086 Book, Osborne/McGraw-Hill, Berkley, Calif., 1980. 21. Rooney, V. M., and Ismail, A. R., Microprocessors and Microcomputers, Macmillan, New York, 1984. 22. Santore, R., 8080 Machine Language Programming for Beginners, Dilithi- um Press, Portland, Ore., 1978. 23. Scanlon, L., IBM PC Assembly Language, Bobert J. Brady Co., Bowie, Md., 1983. 24. Spracklen, K., Z-80 and 8080 Assembly Language Programming, Hayden Book Co., Rochelle Park, New Jersey, 1979. 25. Struble, G. W., Assembler Language Programming: The IBM System/360 and 370, Addison/Wesley, Reading, Mass., 1975.
Литература 421 26. Titus, С. A., Rony, Р., Larsen D. G., Titus, J. A. 8080/8085 Software De- sign, Howard W. Sams & Co., Indianapolis, 1978. 27. Wakerly, J. F., Microcomputer Architecture and Programming, John.Wiley & Sons, New York, 1981. [Имеется русский перевод: Уокерли Дж. Архи- тектура и программирование микроЭВМ: В 2 книгах.— М.: Мир, 1984.] 28. Weller, W. J., Shatzel, А, V., and Nice, Н. Y., Practical Microcomputer Pro- gramming: The Intel 8080, Northern Technology Books, 1976. 29. Zaks, R., Programming the 6502, Sybex, Berkley, Calif., 1978. 30. Zaks, R., 6502 Applications Book, Sybex, Berkley, Calif., 1979. 31. Zaks, R., 6502 Games, Sybex, Berkley, Calif., 1980.
Предметный указатель Адрес 28 — возврата 212 — команды 44 — косвенный 274 — многобайтовой величины 47 — относительный 98 — перехода 99 — 8-разрядный 156 — 16-разрядный 28, 156 — стартовый 168 — 5 фиктивный 287 — эффективный 51 Адресация, таблица 379 — к нулевой странице 270 — косвенная 253, 274 — непосредственная 40 — относительная 98 — пост-индексная косвенная 277 — предындексная косвенная 274 — прямая 253 — с индексацией 51 Аккумулято.р 56 Ассемблер 43 — LISA43 Ассемблирование 171 — ручное 156 Байт 29 — младший 105 — старший 105 Байты переставленные 40 Блок (на ленте) 327 Бод 255 Бит 18 Биты паритета 328 — поперечного паритета 328 — продольного паритета 328 Буфер 90 — кольцевой 260 — стандартный входной 90 Ввод 90 — с клавиатуры 90, 252 Ветвление 68 — и переход 68, 98 Вклинивание бита 327 Возврат из подпрограммы 140, 219 --- прерывания 249 Вход (в подпрограмму) 138 Выборка команды 98 Вызов программ 212 ---на языке ассемблера из Бейси- ка 189 Выпадение бита 327 Выполнение программы 43 ---пошаговое 174 Выражение адресное 48 Выталкивание (из стека) 215 Выход (из подпрограммы) 212 Вычитание, использование в делении 144 — в операции сравнения 72 — двоичное 20 — знаковых 16-разрядных чисел 322 — констант 63 — 8-разрядных чисел 63 -------из 16-разрядных 71 — чисел с основанием 256, 54 -------плавающей точкой 360 Вычисление среднего 314 Группы кодов операций 195 ------- , таблица 389 Дамп 175 Декремент 37 — 2-байтовых величин — и флажок переноса 60 ------- переполнения 206 Деление двоичное 135 — на два 111 422
Предметный указатель 423 -------- знаковое 123 --------16-разрядных чисел 123 ---- десять 128 ---- нуль 136 — чисел с плавающей точкой 359 Дизассемблер 347 Диод световой 264 Дисковая операционная система Эпл DOS 331 Дисплей на жидких кристаллах 265 — семисегментный 265 Дополнение 32 — двухбайтовых величин 108 — до двух 32 ---- десяти 240 ---- единицы 32 Дроби бесконечные 353 — двоичные 352 ----, таблица 352 — десятичные 352 — конечные 353 — шестнадцатеричные 352, Заголовок (программы) 289 Загрузка программы 176 — регистра 34 — указателя стека 221 Заем 20 — , связь с переносом 59 Закрытие файла 333 Замена содержимого памяти (при отладке) 179 Запись (в память) 34 — и флажок переноса 60 Заплата 185 Запрос на прерывание 248 Знак целого числа 31 — числа с плавающей точкой 355 Запоминающее устройство с произ- вольной выборкой (ЗУПВ) 287 Имена временных переменных 229 — команд процессора 6502, таблица 371 — констант 92 массивов, как параметры подпро- грамм 278 — переменных и массивов 70 — файлов 171 — ячеек памяти 29 Индикатор 7-сегментный 265 Индекс 50 — в двумерных массивах 317 — , выход за допустимые границы 168" --- константа 50 ---в длинном массиве 282 --- переменная 51 — , содержащий константу 83 Индексирование в длинных массивах 282 — на нулевой странице 273 — отрицательное 324 --- , недостатки 326 Инициализация переменных 161 Инкремент 37 — 2-байтовых величин 107 Интерактивная программа 153 Интерпретатор 342 — смешанный 343 — чистый 343 Ключевые слова 343 ---зарезервированные 343 Код 13 — знака 31 — исходный 190 — команды 40 — объектный 190 — операции 40 — режима адресации 195 — типа 362 Коды ASCII 89 ---, таблица 380 Команда вызова подпрограммы 212 Команды диагностические 184 — индексные 51 — монитора Эпл 174 -------, табл. 388 — , использующие стек 218 — пересылки 65 — процессора 34 --- , таблицы 371 — с адресацией к нулевой странице 270
424 Предметный указатель — системы LISA 170 ------- , таблица 387 — с непосредственной адресацией 40 — сравнения и переполнение 206 Компилятор 347 Компромисс время — память 131 ------- , критерии 132 Константа 35 — символьная 87 Кросс-ассемблер 341 Лента (магнитная) 327 Листинг 171 Логическое ВКЛЮЧАЮЩЕЕ ИЛИ 200 — И 193 — ИЛИ 197 — ИСКЛЮЧАЮЩЕЕ ИЛИ 199 Мантисса (в числах с плавающей точкой) 355 Маска 193 Маскирование 194 Массив 49 — 2-байтовых величин 114 — двумерный 317 ---, развертывание по строкам 319 ---,-------столбцам 319 — длинный 281 — , ограничение размера 52 — , параллельный 114 — последовательный 115 — строк 299 — упорядоченный 307 — шестнадцатеричных цифр 303 Метка 69 — локальная 103 Микропрограммирование 341 Мнемоника 41 — , таблица 371 Модификация адреса 286 — данных в командах с непосредст- венной адресацией 292 — относительного адреса 296 — команд 286 ----, недостатки 286 Монитор 174 Мусор 161 Назначение регистра 76 наложение 167 — секции данных и программной 45 Нарушение границ команд 167 Нисходящее проектирование 350 Нули лидирующие 20 Обработка битов 125 Объявление констант 96 — символьных строк'95 Округление 112 Операнд 41 Операции с плавающей точкой 358 Операционная система DOS 331 Оптимизация программ 68 Основание системы счисления 19 Остаток (в делении) 142 Отладка программ 174 ---- с точками останова 178 Очередь входная 260 — выходная 260 Очистка (регистра) 56 Ошибка в задании стартового адреса 168 Ошибки наложения 167 — смешивания 166 — повторяющиеся 161 Память внешняя 29 — вспомогательная 29 — оперативная 28 — эмулированная 340 Параметры подпрограммы 288 ---- фактические 289 ----формальные 289 Паритет 125 Передача данных между LISA и Бейсиком 191 Перенос 19 — внутренний (межразрядный) 207 — и переполнение 205 — как индикатор ошибки 142 — , связь с займом 59
Предметный указатель 425 Переполнение 205 — стека 225 Переход 68 — если ложь 309 ---- истина 309 — по минусу 101 ----ненулевому результату 73 ---- неравенству 73 ---- равенству 73 ----условию больше чем или равно 103 -------меньше чем или равно 103 -------сброшенного флажка пере- носа 69 ------------- переполнения 206 -------установленного флажка пе- реноса 69 ------------- переполнения 206 — условный 73 ----, время выполнения 129 ---- , границы 100 Постоянное запоминающее устройст- во (ПЗУ) 287 Подпрограмма 24 — деления 142 — для вычислений с плавающей точ- кой 358 — с параметрами 275 — умножения 137 Подпрограммы монитора Эпл, таб- лица 382 Подсказка (символ) 171 Поиск в таблице 128 ----массиве строк 300 ----упорядоченном массиве 313 — двоичный 183 Полубайт 303 Полуслово 46 — левое 46 — младшее 46 — правое 46 — старшее 46 Порядок (в числе с плавающей точ- кой) 355 — смещенный (в числе с плавающей точкой) 356 Преамбула программы 289 Представление знаковое в прямом коде 31 — отрицательных чисел 31 — с плавающей точкой 355 --- фиксированной точкой 354 Преобразование входное 147 — выходное 147 — двоичных чисел в десятичные 17 ----------шестнадцатеричные 24 — десятичных чисел в двоичные 17 ----------шестнадцатеричные 24 — 8-разрядных чисел в 16-разрядные 323 — типов 362 — шестнадцатеричных чисел в двоич- ные 24 ----------десятичные 23 Прерывание внешнее 248 — внутреннее 151 — немаскируемое 250 Проверка битов 202 — на нечетность 204 — ручная (программы) 160 Прогонка ручная (программы) 161 Программа «быстрая, но грязная» 133 — законченная 151 — исходная 190 — обработки прерывания 248 — объектная 190 — опроса 261 — основная 151 — реального времени 132 — рекурсивная 237 — эхо (на дисплее) 91 Программирование в реальном вре- мени 264 — структурное 348 Программный счетчик 98 ---эмулированный 340 Проталкивание (в стек) 215 Проход (ассемблера) 346 Псевдокоманда 43 Псевдокоманды, таблица 375
426 Предметный указатель Размер максимальный оперативной памяти 29 --- стека 216 Регистр внутренний 97 — индексный 51 — команд 98 — состояния Р 244 — флажков 244 — эмулированный 340 — А 28 — PC (программный счетчик) 98 — S 219 — X 28, 51 — Y 28, 51 Редактор 336 Режим десятичный 239 — инверсный (дисплея) 87 — нижнего регистра (дисплея) 87 — нормальный (дисплея) 87 — мерцания (дисплея) 87 Режимы адресации 51 --- , таблица 379 Сдвиг арифметический 111 — двойной 122 — логический 111 — 16-разрядных величин 121 — с расширением знака 123 — циклический 121 Секция данных 44 — программная 44 Символ управляющий Control-D 87 ------------Е 171 ------------Н 87 -----------J, —К, — L 46 ------------М 87 ------------О 46 ------------X 46 Символы 87 — управляющие 87 --- , таблица 381 Система восьмеричная 16 — двоичная 16 — десятичная 16 — с основанием 256, 53 — шестнадцатеричная 23 Скорость выполнения команд, таб- лица 371 ---программы 129 Слово состояния программы 244 — машинное 29 Сложение 55 — в двоичной системе 19 ---системе с основанием 256 54 ---шестнадцатеричной системе 25 — 8- и 16-разрядных чисел 57 — , использование в умножении 139 — констант 58 — 8-разрядных чисел 57 — 16-разрядных чисел 57 — с переносом 57 — чисел со знаком 323 ---с плавающей точкой 359 Смещение 82 — в длинном массиве 282 Сортировка 307 Сохранение и восстановление 209 Сохранность регистра 210 Список, обработка 278 — циклический 260 Сравнение 72 — 2-байтовых величин 105 — беззнаковое 102 — в десятичном режиме 240 — знаковое 200 — программ по скорости выполнения 130 — с константой 73 Ссылка вперед 294 Стек 215 — аппаратный 223 — в рекурсивных программах 237 — перевернутый 216 — полный 225 • — пустой 225 Стековое пространство .224 Степени двух, таблица 16 — шестнадцати 23 Страница вторая 270 — нулевая 270 — первая 270 Страницы 270
Предметный указатель 427 Строки символьные 88 ---- распакованные 242 ---- упакованные 242 ---- , упаковка и распаковка 242 Счетчик текущего адреса 109 Таблица истинности 194, 197, 200 — обозначений в ассемблере 346 — упорядоченная 183 Точка двоичная 353 — десятичная 353 — останова 179 — плавающая 355 — фиксированная 354 Трансляция с языка ассемблера 43 Трассировка 176 Узел списка 278 Указатель 278 — на узел списка 278 — стека 219 Умножение 134 • — двоичное 134 — на 2 20, ПО ----10 117 ---- константу 120 — чисел со знаком 322 ----------- на 2 111 ----с плавающей точкой 359 Упорядочение по алфавиту 310 Уровни подпрограмм 235 Файл двоичный 332 — текстовый 333 Флажок внутреннего прерывания В 244 — десятичного режима D 239 • — займа С 60 знака S 101 — логический 309 — нуля Z 73 — переноса С 56 — переполнения V 206 — прерывания I 249 Форма числа внутренняя 146 ---внешняя 146 ---двоичная 146 Цикл вложенный 257 — ожидания 255 — машинный 98 ---, длительность 129 — программный 75 — , расчет скорости выполнения 129 Число 2-байтовое 46 — большое (большее, чем 255) 30 — двоично-десятичное 238 — десятичное 15 — отрицательное 30 — случайное 360 — со знаком 32 -------16-разрядное 46 — с' плавающей точкой нормализо- ванное 356 — 16-разрядное 46 — четное и нечетное 20 — шестнадцатеричное 23 Частное в делении 142 Эмулятор 339 Эквивалентность программ 67 Эффект побочный 38 Язык ассемблера 41 — машинный 41 — промежуточный 343 Ячейка 28
Содержание Предисловие переводчика 5 Предисловие 7 Статья 1. Коды 13 Статья 2. Двоичные числа 15 Статья 3. Двоичные сложение и вычитание 19 Статья 4. Шестнадцатеричная система 22 Статья 5. Шестнадцатеричные сложение и вычитание 25 Статья 6. Регистры, ячейки и байты 27 Статья 7. Многобайтовые величины и дополнение до двух 30 Статья 8. Загрузка регистров и запись в память 34 Статья 9. Инкремент и декремент 37 Статья 10. Машинный язык и язык ассемблера 40 Статья 11. Ассемблер и псевдокоманды 43 Статья 12. Двухбайтовые числа и адресные выражения 46 Статья 13. Индексированные переменные и индексные регистры 49 Статья 14. Система счисления с основанием 256 53 Статья 15. Сложение в процессоре 6502 55 Статья 16. Связь между переносом и займом 59 Статья 17. Вычитание в процессоре 6502 62 Статья 18. Команды пересылки и комментарии 65 Статья 19. Переходы и метки 68 Статья 20. Сравнение, флажок нуля и переходы 72 Статья 21. Циклы 75 Статья 22. Дополнительные сведения о циклах и флажке нуля 79 Статья 23. Смещения 82 Статья 24. Символьные коды 86 Статья 25. Ввод-вывод, подпрограммы и «EQU» Статья 26. Объявления констант 90 94 Статья 27. Программный счетчик и относительная адресация 97 Статья 28. Флажок знака и команды сравнения 101 Статья 29. Сравнение двухбайтовых величин Статья 30. Инкремент, декремент и дополнение двухбайтовых величин Статья 31. Умножение и деление на два 105 107 НО Статья 32. Массивы двухбайтовых величин 114 Статья 33. Умножение на десять 117 Статья 34. Циклический и 16-разрядный сдвиги 121 Статья 35. Обработка битов и паритет 125 Статья 36. Просмотр таблиц и вычисление времени выполнения про- граммы Статья 37. Компромисс между требуемым объемом памяти и временем выполнения 128 131 Статья 38. Двоичные умножение и деление 134 Статья 39. Подпрограмма умножения 137 Статья 40. Подпрограмма деления 142 Статья 41. Входное и выходное преобразования 146 Статья 42. Законченные программы 151 Статья 43. Ручное ассемблирование 155 Статья 44. Ручные проверка и прогонка 160 Статья 45. Ошибки смешивания, наложения и стартового адреса 166
Содержание 429 Статья 46. Команды программы LISA 170 Статья 47. Пошаговое выполнение и трассировка 174 Статья 48. Отладка с точками останова 178 Статья 49. Принцип разрыва телефонной линии и двоичный поиск 182 Статья 50. Заплаты в программах на языке ассемблера 185 Задача 1 для решения на ЭВМ 188 Статья 51. Вызов программ на языке ассемблера из программ на Бей- Статья 52. Логическое И 193 Статья 53. Логическое ИЛИ 197 Статья 54. Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ 199 Статья 55. Проверка битов 202 Статья 56. Флажок переполнения 205 Статья 57. Сохранение и восстановление переменных 209 Статья 58. Адреса возврата и косвенные переходы 212 Статья 59. Стеки 215 Статья 60. Команды, ориентированные на использование стека 218 Задача 2 для решения на ЭВМ 222 Статья 61. Использование аппаратного стека 223 Статья 62. Программа входного преобразования 226 Статья 63. Программа выходного преобразования 230 Статья 64. Преимущества аппаратного стека 235 Статья 65. Двоично-десятичные числа и десятичный режим 238 Статья 66. Упакованные строки ; 241 Статья 67. Регистр состояния 244 Статья 68. Прерывания 247 Статья 69. Команды ввода данных 251 Статья 70. Команды вывода данных 255 Задача 3 для решения на ЭВМ 258 Статья 71. Очереди и метод опроса 258 Статья 72. Звуковое устройство и программирование в реальном вре- мени 262 Статья 73'. Дополнительные средства описания строк 266 Статья 74. Команды для нулевой страницы 270 Статья 75. Предындексная косвенная адресация 274 Статья 76. Постиндексная косвенная адресация 277 Статья 77. Длинные массивы 281 Статья 78. Модификация адресов 295 Статья 79. Параметры подпрограмм 288 Статья 80. Модификация данных в командах с непосредственной ад- ресацией 292 Задача 4 для решения на ЭВМ 295 Статья 81. Модификация относительных адресов 296 Статья 82. Массивы строк 299 Статья 83. Массивы шестнадцатеричных цифр 303 Статья 84. Сортировка 307 Статья 85. Упорядочение по алфавиту 310 Статья 86. Поиск в упорядоченном массиве 313 Статья 87. Двумерные массивы 317 Статья 88. 16-разрядные числа со знаком 321 Статья 89. Отрицательное индексирование 324 Статья 90. Паритет при работе с магнитной лентой 327 Задача 5 для решения на ЭВМ 330 Статья 91. Дисковая операционная система ЭВМ Эпл 331 Статья 92. Дальнейшие сведения о текстовых файлах 336 Статья 93. Эмуляторы 339 Статья 94. Интерпретаторы 342
430 Содержание Статья 95. Ассемблеры и компиляторы 345 Статья 96. Структурное программирование 348 ^Статья 97. Двоичные и шестнадцатеричные дроби 352 Статья 98. Действительные числа и представление с плавающей точкой 355 ’Статья 99. Операции с плавающей точкой 358 Статья 100. Вычисления с преобразованием типов 359 Задача 6 для решения на ЭВМ 363 Приложение 366 Ответы к упражнениям, помеченным звездочками 390 Литература 420 Предметный указатель 422
УВАЖАЕМЫЙ ЧИТАТЕЛЬ! Ваши замечания о содержании книги, ее оформле- нии, качестве перевода и другие просим присылать по адресу: 129820, Москва, И-110, ГСП, 1-й Рижский пер.у д. 2, изд-во «Мир».
МОНОГРАФИЯ Уорд Морер ЯЗЫК АССЕМБЛЕРА ДЛЯ ПЕРСОНАЛЬНОГО КОМПЬЮТЕРА ЭПЛ Научный редактор Т. Н. Шестакова Младший научный редактор М. Н. Стасюк Художник С. А. Бычков Художественный редактор Н. М. Иванов Технический редактор Н. И. Манохина Корректор С. А. Денисова ИБ № 5885 Сдано в набор 26.09.86. Подписано к печати 19.02.87. Формат -60X90V16. Бумага кн.-журн. имп. Печать высокая. Гарнитура литературная. Объем 13,50 бум. л. Усл. печ. л. 27. Усл. кр.-отт. 27. Уч.-изд. л. 23,96. Изд. № 6/4580. Тираж 25 000 экз. Зак. 589. Цена 2 руб. 10 коп. Издательство «Мир» 129820. ГСП, Москва, И-110, 1-й Риж- ский пер., 2. Московская типография № 11 Союзполиграфпрома при Государственном комитете СССР по делам издательств, по- лиграфии и книжной торговли. 113105, Москва, Нагатин- ская ул., д. 1.