\/d Г Л А В А X. УПРАВЛЕНИЕ РЕСУРСАМИ ПАМЯТИ MSX-КОМПЬЮТЕРОВ \/d- Мозг, хорошо устроенный, стоит больше, чем мозг, хорошо наполненный. М.Монтень X.1. К а р т а п а м я т и (д л я к о м п ь ю т е р о в MSX-1) Персональный MSX-компьютер имеет небольшой объем памяти - 96 Кбайтов для MSX-1 и 242 Кбайта для MSX-2. Поэтому полезной для пользователя оказы- ваются информация о распределении ресурсов памяти и сведения о наличии и объеме в ней свободных мест в любой момент времени. Общий объем памяти у компьютеров серии MSX-1 равен 96 Кбайтам. Здесь и далее мы будем рассматривать только 64 Кбайта, с которыми обычно и работа- ет основная масса пользователей. Взгляните на приведенный ниже рис.12 ... Вся память разбита на две основные части: ┌─────────────────────────────────────────────────────────────────────┐ │ ROM ("Read Only Memory" - "Постоянное Запоминающее Устройство") и │ │ RAM ("Random Access Memory" - "Оперативное Запоминающее Устройство")│ └─────────────────────────────────────────────────────────────────────┘ ROM содержит те программы и данные, которые "заложены" в компьютер при изготовлении. Вот почему он всегда выводит определенные сообщения при включении и способен "понимать" программу на языке MSX-BASIC. В ROM находится и н т е р п р е т а т о р - программа на машинном язы- ке, которая переводит один за другим операторы языка MSX-BASIC в програм- му на машинном языке, т.е. на е д и н с т в е н н о м языке, который по- нимает компьютер. С помощью этой программы компьютер проверяет синтаксис, выводит при необходимости сообщение об ошибке, переходит к следующему опе- ратору или возвращается в командный режим и так далее. Здесь же находятся подпрограммы управления клавиатурой и экраном, кото- рые составляют э к р а н н ы й р е д а к т о р MSX-BASIC. ROM в основном разделена на две части: 1) подпрограммы BIOS ("Basic Input-Output System"); 2) другие подпрограммы. Так, например, при включении компьютера насту- пает небольшая пауза; в этот момент происходят различные инициализации эк- рана дисплея (установка определенного режима SCREEN, установка ширины эк- рана WIDTH и др.). Это происходит оттого,что "зашитые" в ROM подпрограммы инициализации "посылают" определенную информацию в рабочую область RAM, разговор о которой еще пойдет впереди. Подпрограммы BIOS осуществляют переход к другим подпрограммам. Они на- поминают последовательность операторов GOSUB, которую можно увидеть на первом уровне хорошо структурированной программы на MSX-BASIC. Подпрограм- мы BIOS расположены по одним и тем же адресам ROM независимо от версии MSX-BASIC и осуществляют переход к другим подпрограммам, положение кото- рых может быть изменено. В противоположность ROM RAM не сохраняет информацию при выключении ком- пьютера. Сейчас мы расскажем Вам о структуре RAM. "Верхушка" памяти (она изображена в н и ж н е й части таблицы) за- нята р а б о ч е й о б л а с т ь ю, которая состоит из: α) таблицы системных переменных, β) таблицы ловушек ("Hooks Table"). "Нижняя" область памяти (она изображена в в е р х н е й части таблицы) занята: 1) текстом программы ("Program Instruction Table", PIT); 2) таблицей переменных ("Variable Table", VT). VT содержит все перемен- ные, создаваемые в ходе выполнения программы; 3) таблицей массивов ("Array Variable Table"). Между "верхней" и "нижней" областями памяти располагаются: α) свободная область ("Free Area"); β) с т е к ("Stack Area"); стек содержит всю информацию, необходимую для выполнения программы. Например, именно здесь хранится адрес тех бай- тов PIT, которые содержат сведения о следующем выполняемом операторе Ва- шей программы; γ) с т р о к о в а я область ("Character String Area"); по умолчанию для нее отводится 200 байтов, однако размеры этой области можно менять оператором CLEAR (см. раздел X.7.); δ) б л о к у п р а в л е н и я файлами ("Files Control Block"). Если в Вашей программе присутствует оператор MAXFILES= (напомним, что он задает максимальное количество одновременно открытых файлов), то для каждого файла автоматически резервируется 267-байтное пространство для осуществления обмена информацией с файловыми устройствами. &H0000 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧∗ ∗▧▧▧▧▧▧▧▧▧ ROM (Интерпретатор MSX-BASIC) ▧▧▧▧▧▧▧∗ ∗▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧∗ ─ ─ ─ ─∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗─ ─ ─ ─ &H8000 │ │ TXTTAB │ Программа на языке BASIC │ │ │ ("Program Instruction Table", PIT) │ ▼ │ │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │─ ─ ─ ─ │ │ VARTAB │ Простые переменные ("Variable Table") │ │ │ │ ▼ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │─ ─ ─ ─ │ Массивы ("Array Variable Table") │ ARYTAB │ │ │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ▼ │ │ │ С в о б о д н а я о б л а с т ь ("Free Area")│ │ │ R A M │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │ │ ▲ │ Стек ("Stack Area") │ │ │ │ STKTOP │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤─ ─ ─ ─ │ │ ▲ │ Строковая область ("Character String Area") │ │ │ │ FRETOP │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤─ ─ ─ ─ │ │ FILTAB │ Блок управления файлами ("Files Control Block")│ │ │ │ ▼ ─ ─ ─ ─ ┼────────────────────────────────────────────────│─ ─ ─ ─ &HF380 │ Таблица системных переменных │ HIMEM │ ("System Variable Table") │ │ ─ ─ ─ ─ │─ ─ ─ ─ ─ - - - - - - - - - - - - - - - - ─ ─ ─ │ ▼ &HF3A9 │ │ │ Таблица ловушек ("Hooks Table") │ &HFFFF │ │ ─ ─ ─ ─ └────────────────────────────────────────────────┘ Р и с. 12 Приведенная карта памяти справедлива и для компьютеров серии MSX-2. Но в отличие от компьютеров серии MSX-1 с объемом ROM в 32 Кбайта и RAM в 64 Кбайта, компьютеры серии MSX-2 имеют гораздо больший объем памяти (108 Кбайтов ROM и 134 Кбайта RAM). Спрашивается, где размещается эта память? ┌────────────────────────────────────────────────────┐ │ Оказывается, вся память ПЭВМ разбита на блоки │ │ объемом по 64 Кбайта, называемые с л о т а м и ! │ └────────────────────────────────────────────────────┘ Однако рассмотрение этого вопроса потребует от читателя дополнительных знаний, и поэтому мы рассмотрим его позднее (см. Приложение 1.8.2). Память разделена на ячейки (б а й т ы), каждая из которых имеет адрес, закодированный д в у м я байтами: А д р е с я ч е й к и (байта) ┌─────────────────┬─────────────────┐ │ 1 1 0 1 1 0 1 1 │ 1 0 1 1 1 1 0 1 │ └────────▲────────┴────────▲────────┘ └── Б а й т ы ──┘ Поэтому максимально большой адрес байта равен 256·&B11111111+&B11111111+1 = 256·&hFF+&HFF+1 = 65535+1 = 65536 , а следовательно, и обратиться можно не более чем к 65536 ячейкам памяти (подумайте, почему производится умножение именно на 256?). Говорят, что "объем непосредственно адресуемой памяти - 65536 байтов". X.2. Ф у н к ц и я PEEK и о п е р а т о р POKE Функция PEEK позволяет Вам "посмотреть" содержимое любой ячейки памяти в адресном пространстве MSX-компьютера. Ее общий вид: ┌─────────────────────┐ │ PEEK (а д р е с) │ , └─────────────────────┘ где: PEEK ("to peek"-"заглядывать") - служебное слово; а д р е с - арифметическое выражение, значение которого находится в диапазоне от &h0 до &hFFFF. Функция PEEK возвращает целое число в интервале от 0 до 255, содержаще- еся в проверяемой ячейке памяти. Например: 1) 10 WIDTH 7:? PEEK(&HF3B0) 2) 10 SCREEN 2:PSET(15,18):SCREEN0:PRINT run PEEK(&HFCB3);PEEK(&HFCB5) 7 run Ok 15 18 Ok В первом примере мы "попросили" компьютер вывести на экран содержимое ячейки с адресом &HF3B0 (в байте по этому адресу хранится значение систем- ной переменной - длины дисплейной строки). Во втором примере мы использо- вали информацию из таблицы адресов системных переменных (см.Приложение 2). Величину, возвращаемую функцией PEEK, можно интерпретировать как код символа, команду MSX-BASIC, номер строки, число, "часть" числа, "храняще- гося" в нескольких байтах, и т.д. В некоторых случаях правильную интерпре- тацию можно дать по контексту, однако, если должной уверенности нет, надо а н а л и з и р о в а т ь не только содержимое одной ячейки, но и содер- жимое ячеек, находящихся в ее "окрестности"! П р и м е р 1. 10 X=&H8000'"Заглянем" в память,начиная с адреса &H8001! ───────────── 20 X=X+1:Y=PEEK(X) 30 IF Y<32 THEN ?"│";:GOTO 20 ELSE ?CHR$(Y);" ";:GOTO20 Наличие условия Y<32 в строке 26 связано с тем, что существуют "непеча- таемые" символы, имеющие код ASCII, меньший 32. Поговорим теперь об очень полезном операторе POKE. Общий вид оператора: ┌───────────┐ │ POKE A,D │ └───────────┘ где: POKE ("to poke"-"помещать") - служебное слово; A - арифметическое выражение, значение которого находится в диапазо- не от &h8000 до &hFFFF; D - арифметическое выражение, значение которого принадлежит отрезку [0,255] (поскольку оно должно умещаться в один байт). Оператор POKE вычисляет значения выражений А и D и сохраняет значение D (которое должно помещаться в одном байте!) по адресу А. Обратите внима- ние на то, что значение А может оказаться о т р и ц а т е л ь н ы м! Если значение А не удовлетворяет ограничениям, то компьютер сообщит об ошибке: "Overflow" ("П е р е п о л н е н и е"), а если значение D, - то "Illegal function call" ("Н е п р а в и л ь н ы й в ы з о в ф у н к ц и и"). Вы можете использовать оператор POKE для: α) модификации текста Вашей программы; β) изменения значений переменных; γ) размещения в RAM программы, написанной на машинном языке (ее запись производится п о б а й т н о). Более того, этот оператор позволяет Вам экспериментировать с "подвалом" компьютера (рабочей областью). Но делайте так только в том случае, если Вы понимаете, что за этим последует! П р и м е р 2. Сравните результаты работы двух программ: ───────────── 10 SCREEN 1:PRINT"A" 10 SCREEN 1:PRINT"A" 20 WIDTH 10 20 POKE &HF3B0,10 X.3. Т а б л и ц а п р о г р а м м н ы х к о м а н д (PIT) Таблица PIT обычно начинается по адресу &H8000. Однако ее можно "сдви- нуть", изменив значение системной переменной TXTTAB в таблице системных переменных. П р и м е р 1. Для помещения PIT с адреса &HА000 достаточно выполнить ───────────── следующую программу: 5 'Адрес &HА001,находящийся в двух ячейках с номерами,начиная с &hF676 (слове TXTTAB (&HF676)),определяет место,с которого начнется текст про- граммы 10 POKE &HF676,&H01 'Заполнение младшего байта слова TXTTAB 20 POKE &HF677,&HA0 'Заполнение старшего байта слова TXTTAB 30 POKE &HA000,0 'Первый байт PIT(&HA000) должен быть нулевым! 40 NEW 'Стираем данную программу! Напомним Вам, что адрес &HА001 (как и любой другой!) размещается в двух байтах так: Адрес ──▶ &HF676 &HF677 ◀── Адрес младшего байта │ │ старшего байта ┌─────│─────────│────┐ Содержимое │ ┌───▼──┐ ┌───▼──┐ │ Содержимое младшего ◀───── &Н01 │ │ &HА0 ─────▶ старшего байта │ └──────┘ └──────┘ │ байта └────────────────────┘ Слово TXTTAB Очевидно, что в результате этих "манипуляций" размер свободной области уменьшится на &H2000 байтов (&HA000-&H8000=&H2000), и область, расположен- ная между &H8000 и началом PIT, будет защищена от "вторжения" программ на MSX-BASIC и, следовательно, "б е з о п а с н а" для программ на машинном языке. Ясно, что величина PIT зависит от размера текста программы. После выполнения данной программы нажмите кнопку сброса "RESET". А теперь мы расскажем Вам о том, как хранится программа, написанная на языке MSX-BASIC в PIT. ┌───────────────────────────────────────────────────────────────────────┐ │ Все строки программы на MSX BASIC начинаются с д в у х б а й т - │ │ н о г о указателя. │ │ За этим указателем идут д в а байта, содержащие номер строки. │ │ Затем идет текст строки с последующим нулевым байтом. │ │ За последней строкой следуют д в а дополнительных нулевых байта, │ │ адрес которых находится в указателе последней строки программы. │ │ Цифры и зарезервированные служебные слова записываются во внутрен- │ │ нем коде (один или два байта на слово, цифру) │ │ Для остального текста используется код ASCII. │ └───────────────────────────────────────────────────────────────────────┘ П р и м е р 2. Введем в память следующую короткую программу: ───────────── 10 B=5 20 END Теперь прочитаем, что же реально содержится в PIT, используя в непо- средственном режиме команду PRINT HEX$(PEEK(A)) , где значение переменной А (адреса) изменяется от &H8000 до &H8010. Вы обнаружите: ┌───────────┬─────────────┬─────────────────────────────────────────────┐ │ Значение А│HEX$(PEEK(A))│ К о м м е н т а р и и │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8000 │ 0 │ Первый байт PIT всегда нулевой │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8001 ∗∗│ 09 │ Указатель первой строки "говорит" нам, что │ │∗∗ 8002 ∗∗│ 80 │ указатель следующей строки находится по ад- │ │∗ ∗ ∗ ∗ ∗ ∗│ │ ресу &Н8009 │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8003 │ А │ Номер первой строки &H000А = 10 │ │ 8004 │ 0 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8005 │ 42 │ Шестнадцатеричный код ASCII буквы "B" │ │ 8006 │ EF │ Внутренний код знака равенства │ │ 8007 │ 16 │ Внутренний код цифры 5 │ │ 8008 │ 0 │ Конец первой строки │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8009 ∗∗│ 0F │ Указатель второй строки показывает, что │ │∗∗ 800A ∗∗│ 80 │ указатель следующей строки находится по │ │∗∗∗∗∗∗∗∗∗∗∗│ │ адресу &H800F │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800B │ 14 │ Номер второй строки &H0014 = 20 │ │ 800C │ 00 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800D │ 81 │ Внутренний код оператора END │ │ 800E │ 0 │ Конец второй строки │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800F │ 0 │ Конец программы │ │ 8010 │ 0 │ │ └───────────┴─────────────┴─────────────────────────────────────────────┘ Теперь, надеемся, Вам стало ясно, как можно изменить программу с помо- щью оператора POKE. Попробуйте выполнить следующее: POKE &H8005,&H41 '41 - шестнадцатеричный код ASCII буквы "A" POKE &H8007,&H17 '17 ─ внутренний код цифры "6" А теперь наберите команду LIST, затем нажмите клавишу◀─┘ и ...: 10 A=6 20 END П р и м е р 3. Теперь Вам ясно,что "инструкции" PEEK и POKE таят в се- ───────────── бе поистине безграничные возможности. По существу, они позволяют нам распоряжаться памятью компьютера по своему усмотрению. Например, они позволяют нам при желании подшутить над компьютером: ес- ли известно, где хранится программа, то мы можем сделать так, что после одной из строк программы окажется строка с м е н ь ш и м номером. Пусть исходная программа имеет вид: 10 PRINT 4 20 PRINT 2 Вам, конечно, уже известно, что строки программы на языке MSX-BASIC на- чинаются с двухбайтного указателя, за которым следуют два байта, содержа- щие номер строки. Поэтому вначале выполним команду: PRINT HEX$(PEEK(&H8002));" ";HEX$(PEEK(&H8001)) 80 9 Ok Таким образом, указатель следующей (с номером 20) строки располагается в ячейках с адресами &H8009 и &H800A, а, следовательно, номер второй стро- ки находится в ячейках с адресами &H800B и &H800C . Проверим этот факт: PRINT HEX$(PEEK(&H800C));" ";HEX$(PEEK(&H800B)) ┌──▶ PRINT &H14 0 14 ────────────────────────────────────────────┘ 20 Ok Ok А теперь: POKE &H800B,1 Ok list 10 PRINT 4 1 PRINT 2 Ok Программа действует, но строку с номером 1 нельзя ни стереть, ни испра- вить. Вы можете написать еще одну 1-ю строку и даже новую 20-ю строку! П р и м е р 4. Введем в память короткую программу: ───────────── 10 FOR AB=-2.23227 TO 7 STEP 32.671533782376# Теперь "просмотрим" содержимое PIT, используя в непосредственном режи- ме простейшие команды: A=···: PRINT HEX$(PEEK(A)) , где значение переменной А (адреса) изменяется от &H8000 до &H8020. Мы обнаружим массу интересных вещей: ┌───────────┬─────────────┬─────────────────────────────────────────────┐ │ Значение А│HEX$(PEEK(A))│ К о м м е н т а р и и │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8000 │ 0 │ Первый байт PIT всегда нулевой │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8001 ∗∗│ 21 │ Указатель первой строки "говорит" нам, что │ │∗∗ 8002 ∗∗│ 80 │ указатель следующей строки находится по ад- │ │∗ ∗ ∗ ∗ ∗ ∗│ │ ресу &Н8021 │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8003 │ А │ Номер первой строки &H000А = 10 │ │ 8004 │ 0 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8005 │ 82 │ Код служебного слова FOR │ │ 8006 │ 20 │ Внутренний код символа "пробел" │ │ 8007 │ 41 │ Шестнадцатеричный код ASCII буквы "A" │ │ 8008 │ 42 │ Шестнадцатеричный код ASCII буквы "B" │ │ 8009 │ EF │ Внутренний код символа "=" │ │ 800A │ F2 │ Внутренний код символа "-" │ │ 800B │ 1D │Указатель на тип одинарной точности (знак"!")│ │ 800C │ 41 │ Вторая цифра числа указывает на порядок │ │ │ │ минимального значения параметра цикла │ │ 800D │ 22 │ 1 и 2-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 800E │ 32 │ 3 и 4-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 800F │ 27 │ 5 и 6-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 8010 │ 20 │ Внутренний код символа "пробел" │ │ 8011 │ D9 │ Код служебного слова TO │ │ 8012 │ 20 │ Внутренний код символа "пробел" │ │ 8013 │ 18 │ Внутренний код символа "7" │ │ 8014 │ 20 │ Внутренний код символа "пробел" │ │ 8015 │ DC │ Код служебного слова STEP │ │ 8016 │ 20 │ Внутренний код символа "пробел" │ │ 8017 │ 1F │ Указатель на тип двойная точность (знак #) │ │ 8018 │ 42 │ Вторая цифра числа указывает на порядок шага│ │ 8019 │ 32 │ 1 и 2-я цифры мантиссы шага │ │ 801А │ 67 │ 3 и 4-я цифры мантиссы шага │ │ 801B │ 15 │ 5 и 6-я цифры мантиссы шага │ │ 801C │ 33 │ 7 и 8-я цифры мантиссы шага │ │ 801D │ 78 │ 9 и 10-я цифры мантиссы шага │ │ 801E │ 23 │ 11 и 12-я цифры мантиссы шага │ │ 801F │ 76 │ 13 и 14-я цифры мантиссы шага │ │ 8020 │ 0 │ Конец строки │ │ 8021 │ 0 │ Конец программы │ └───────────┴─────────────┴─────────────────────────────────────────────┘ Однако не пытайтесь изменять с помощью оператора POKE длину строки или путать указатели: результат будет к а т a с т р о ф и ч е с к и м! Если Вы хотите защитить свою программу от "постороннего взгляда" (ко- манды LIST), то примените в непосредственном режиме команду: POKE &H8001,1 Ok (разумеется, Ваша программа должна располагаться с адреса &H8000). Ну а если Вы нечаянно нажали RESET, - не спешите отчаиваться! Вашу про- грамму еще можно спасти. Это очень легко сделать, набрав ту же команду POKE &H8001,1 а затем auto . На экране появятся строки: 10* 20* и так далее ... Строки с "*" - спасенные. Теперь достаточно "скомандовать": LIST и ..., о, чудо! Но это еще не все! Оказывается, спасены и все строки между теми, номера которых не делятся нацело на 10! Если же Вы захотите защитить свою программу от запуска (команды RUN), то примените в непосредственном режиме команду: POKE &H8000,1 Ok (разумеется, Ваша программа должна располагаться с адреса &H8000). X.4. Т а б л и ц а п е р е м е н н ы х (VT) Непосредственно следующая за PIT таблица VT начинается с адреса, ука- занного в слове VARTAB, хранящегося по адресу &HF6C2 в области системных переменных (см. Приложение 2). Ее длина зависит от количества используе- мых переменных (скалярных и массивов) и их типов. Отметим, что переменные и массивы хранятся в порядке их создания. Будем говорить, что один программный объект "располагается в памяти в ы ш е другого", если адрес, с которого он расположен, больше. ┌───────────────────────────────────────────┐ │ Массивы хранятся в ы ш е переменных. │ └───────────────────────────────────────────┘ Это значит, что всякий раз, когда интерпретатор встречает новую скаляр- ную переменную, все массивы сдвигаются "в в е р х", чтобы высвободить про- странство. Это может значительно замедлить выполнение программы! ┌─────────────────────────────────────────────────────────────────────┐ │ Во избежание этого, описывайте все скалярные переменные и │ │ массивы в начале программы оператором DIM ! │ └─────────────────────────────────────────────────────────────────────┘ Теперь мы расскажем Вам о важной функции VARPTR, которая указывает ад- рес расположения данных в оперативной памяти. Ее синтаксис: ┌──────────────┐ │ VARPTR(γ) │, └──────────────┘ где: VARPTR("VARiable PoinTeR"-"указатель переменной") - служебное слово; γ - идентификатор ч и с л о в о й переменной. Функция VARPTR возвращает а д р е с X байта RAM, начиная с которого располагается значение переменной γ. Если переменная не существует, то выдается сообщение: "Illegal function call" . П р и м е р. Будьте бдительны! ─────────── 10 INPUT Z 20 PRINT VARPTR(Z) run run ? 0 ? ◀── Нажата клавиша "RETURN" -32743 Illegal function call in 20 Ok Ok Функцию VARPTR часто используют совместно с функцией PEEK и оператором POKE соответственно для просмотра или изменения значения переменной. X.4.1. Х р а н е н и е п р о с т ы х п е р е м е н н ы х Ты славно роешь землю, старый крот! Годишься в рудокопы. У.Шекспир. Гамлет Как уже неоднократно упоминалось, ц е л о е число кодируется в двух байтах. Меньший по адресу байт называется с т а р ш и м , больший по ад- ресу байт - м л а д ш и м. Однако напомним Вам, что ┌─────────────────────────────────────────────────────────┐ │ процессор Z80 "хранит" младший байт "перед" старшим │. └─────────────────────────────────────────────────────────┘ Когда ц е л о ч и с л е н н а я переменная получает значение, процес- сор записывает в оперативную память следующие п я т ь байтов информации: 1) число 2 ("паспорт" VALTYPE), которое означает, что переменная явля- ется целочисленной (значение "паспорта" занимает два байта); 2) код ASCII первого символа имени переменной; 3) код ASCII второго символа имени (0, если имя состоит из одного сим- вола); 4) младший байт значения; 5) старший байт значения. ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ 1-й байт │ │ 2-й байт │ │ 3-й байт │ │ 4-й байт │ │ 5-й байт │ └─────▲─────┘ └─────▲─────┘ └─────▲─────┘ └─────▲─────┘ └─────▲─────┘ │ │ │ │ │ │ Код ASCII Код ASCII Младший байт Старший байт VALTYPE первого символа второго символа значения значения имени переменной имени переменной Оказывается, что адрес четвертого байта (младшего байта значения число- вой переменной) возвращает как раз переменная VARPTR! Кроме того,напомним, что "содержимое" байта с известным адресом может быть "прочитано"функцией PEEK. Перед тем как работать с нижеприведенным примером, во избежание расхож- дений в результатах не забудьте "почистить" память компьютера оператором CLEAR. П р и м е р 1. A%=356:PRINT HEX$(VARPTR(A%)) ───────────── 8006 Ok Вы получили шестнадцатеричный адрес младшего байта значения переменной. ┌───────────────┬────────────────────────────────────────────────────────┐ │? РЕЕК(&Н8003) │Этот адрес соответствует байту VALTYPE. Поэтому Вы долж-│ │ │ ны получить 2. │ │? РЕЕК(&Н8004) │Этот адрес соответствует первому символу имени перемен-│ │ │ной. Вы должны получить число 65, которое является ко-│ │ │ дом ASCII символа "А". │ │? PEEK(&H8005) │Получили число 0, поскольку имя переменной состоит лишь │ │ │ из одного символа. │ │? РЕЕК(&Н8006) │Этот адрес, возвращенный функцией VARPTR, соответствует │ │ │младшему байту значения. Должно быть получено число 100.│ │? РЕЕК(&Н8007) │ Этот адрес соответствует старшему байту значения. │ │ │ Вы, конечно же, получите число 1. │ └───────────────┴────────────────────────────────────────────────────────┘ Перепишем два последних значения в двоичной системе счисления: 100 = 0110 0100 1=0000 0001 ² ² А теперь примем во внимание инверсию порядка байтов: 0000 0001 0110 0100 ──▶ 356 ────▲──── ────▲──── 10 │ │ 1 100 И перед выполнением следующего примера не забудьте ввести в память ком- пьютера оператор CLEAR ! П р и м е р 2. A%=-356:PRINT HEX$(VARPTR(A%)) ───────────── 8006 Ok Полученный результат - это шестнадцатеричный адрес младшего байта. ┌───────────────┬────────────────────────────────────────────────────────┐ │ ? РЕЕК(&Н8003)│ Вы должны получить 2. │ │ ? РЕЕК(&Н8004)│Этот адрес соответствует первому символу имени перемен-│ │ │ ной. Вы должны получить 65 (код ASCII символа "A"). │ │ ? PEEK(&H8005)│ 0, т.к. имя переменной состоит лишь из одного символа.│ │ ? РЕЕК(&Н8006)│Этот адрес, возвращаемый функцией VARPTR, соответствует │ │ │ инвертированному младшему байту значения. │ │ │ Разумеется, Вы получите число 156. │ │ ? РЕЕК(&Н8007)│Этот адрес соответствует инвертированному старшему бай-│ │ │ ту значения. Вы получите число 254. │ └───────────────┴────────────────────────────────────────────────────────┘ Перепишем два последних значения в двоичной системе счисления: 156 = 1001 1100 254=1111 1110 ² ² Принимая во внимание инверсию порядка байтов, запишем их содержимое: 1111 1110 1001 1100 ────▲──── ────▲──── │ │ 254 156 А теперь учитывая, что двоичное число 1111111010011100 записано в до- полнительном коде, получим ² 1111 1110 1001 1100 ──▶ 0000 0001 0110 0011 ──▶ 0000 0001 0110 0011+ ² ² +1 = 0000 0001 0110 0100 = 101100100 = 356. Ура! ² ² Приведем схему расположения информации в памяти для простой числовой переменной о д и н а р н о й и д в о й н о й точности: Байт, адрес которого возвращает функция VARPTR() │ ┌───┬───┬───┬─▼─┬───┬───┬───┬───┐ │▧▧▧│▧▧▧│▧▧▧│∗∗∗│███│███│...│███│ └───┴───┴───┴───┴───┴───┴───┴───┘ Адреса──▶ X-3 X-2 X-1 X └───────▲···────┘ ▲ ───▲─── ▲ │ │ │ │ Значение │ │ Знак и порядок │ Идентификатор Тип переменной Прежде чем проверить работу ниже приведенной командной строки,наберите команду CLEAR. П р и м е р 3. AR!=22.3210E+4:PRINT HEX$(VARPTR(AR!)) ───────────── 8006 Ok Если Вы прочитаете адреса с &H8006-&H3=&H8003 по &H8006+&H3=&H8009, то обнаружите: &H8003 4 VALTYPE п е р е м е н н о й о д и н а р н о й точ- ности (ее значение занимает 4 байта) &H8004 65 Код ASCII символа "А" &H8005 82 Код ASCII символа "R" &H8006 70 = &B 0100 0110 Адрес &H8006 содержит порядок, который кодируется следующим образом: ┌───────────────────────────────────────┬──────────┐ │ Содержимое памяти по адресу &H8006 │ │ ├──────────────────┬────────────────────┤ Порядок │ │ Двоичное значение│ Десятичное значение│ │ ├──────────────────┼────────────────────┼──────────┤ │ 01 000000 │ 64 │ 0 │ │ 01 000001 │ 65 │ 1 │ │ ··· │ ··· │ ··· │ ──▶ 01 000110 │ 70 │ 6 ◀── │ ··· │ ··· │ ··· │ │ 01 111111 │ 127 │ 63 │ │ 00 111111 │ 63 │ - 1 │ │ 00 111110 │ 62 │ - 2 │ │ ··· │ ··· │ ··· │ │ 00 000001 │ 1 │ -63 │ │ 00 000000 │ 0 │ -64 │ └────▲─────────────┴────────────────────┴──────────┘ └── Бит знака мантиссы Величина порядка задается формулой: ┌───────────────────────────────────────────────────────┐ │ Порядок = Двоичное значение 7 младших битов - 64 │ └───────────────────────────────────────────────────────┘ Первый бит байта содержит з н а к м а н т и с с ы , причем 0 соот- ветствует знаку "+", а 1 соответствует знаку "-". Продолжим наши исследования: &H8007 34 = &B 0010 0010 = 22 в двоично-десятичной системе &H8008 50 = &B 0011 0010 = 32 в двоично-десятичной системе &H8009 16 = &B 0001 0000 = 10 в двоично-десятичной системе В трех последних байтах Вы, конечно же, "узнаете" число 225. Итак, три последних адреса содержат мантиссу, записанную в двоично-де- сятичном виде (.223210). ┌──────────────────────────────────────────────────────────────┐ │ Мантисса числа одинарной точности занимает 3 байта, которые │ │ позволяют закодировать 6 разрядов в двоично-десятичном виде.│ └──────────────────────────────────────────────────────────────┘ Перед тем как выполнить предлагаемые в примере действия,наберите и вве- дите в память компьютера оператор CLEAR. П р и м е р 4. AR#=-22.321054981117E-4:PRINT HEX$(VARPTR(AR#)) ───────────── 8006 Ok Если Вы прочитаете адреса с &H8006-&H3=&H8003 по &H8006+&H3=&H8009, то обнаружите: &H8003 8 VALTYPE переменной д в о й н о й точности (ее значение занимает 8 байтов) &H8004 65 Код ASCII символа "А" &H8005 82 Код ASCII символа "R" &H8006 190 = &B 1011 1110 ▲ └── Знак мантиссы отрицательный! Подсчитаем теперь величину порядка: print &B0111110-64 -2 Ok "Продолжим наши игры!": &H8007 34 = &B 0010 0010 = 22 в двоично-десятичной системе &H8008 50 = &B 0011 0010 = 32 в двоично-десятичной системе &H8009 16 = &B 0001 0000 = 10 в двоично-десятичной системе &H800A 84 = &B 0101 0100 = 54 в двоично-десятичной системе &H800B 152 = &B 1001 1000 = 98 в двоично-десятичной системе &H800C 17 = &B 0001 0001 = 11 в двоично-десятичной системе &H800D 23 = &B 0001 0111 = 17 в двоично-десятичной системе В семи последних байтах "узнается" число 0.22321054981117. ┌─────────────────────────────────────────────────────────────────┐ │ Мантисса числа двойной точности занимает 7 байтов, которые │ │ позволяют закодировать 14 разрядов в двоично-десятичном виде │ └─────────────────────────────────────────────────────────────────┘ Подведем и т о г и всему сказанному о хранении числовых переменных. ┌─────────┬───────┬──────┬──────┬───────────────────────────────────────┐ │ Т и п │Значен.│Колич.│Номера│ Значение (система счисления) │ │ │VALTYPE│байтов│байтов│ │ ├─────────┼───────┼──────┼──────┼───────────────────────────────────────┤ │Ц е л а я│ 2 │ 5 │ -3 │ VALTYPE=2 (двоичная) │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -2 │ Первый символ имени (код ASCII) │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -1 │ Второй символ имени (код ASCII) │ │ "Содержимое" этого ├──────┼───────────────────────────────────────┤ │ байта возвращает ───▶│ 0 │ З н а ч е н и е (двоичная); │ │ функция VARPTR() │──────┤для записи отрицательных чисел применя-│ │ │ │ │ 1 │ ется двоичный дополнительный код; │ ├─────────┼───────┼──────┼──────┼───────────────────────────────────────┤ │Одинарной│ 4 │ 7 │ -3 │ VALTYPE=4 (двоичная) │ │ точности│ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -2 │ Первый символ имени (код ASCII) │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -1 │ Второй символ имени (код ASCII) │ │ "Содержимое" этого ├──────┼───────────────────────────────────────┤ │ байта возвращает ───▶│ 0 │ Порядок и знак (знак в первом бите) │ │ функция VARPTR() │ │ (двоичная) │ │ │ │ │ │ Величина порядка = двоичному значению │ │ │ │ │ │ семи последних битов - 64 │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ 1÷3 │ М а н т и с с а (двоично-десятичная) │ ├─────────┼───────┼──────┼──────┼───────────────────────────────────────┤ │ Двойной │ 8 │ 11 │ -3 │ VALTYPE = 8 (двоичная) │ │ точности│ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -2 │ Первый символ имени (код ASCII) │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ -1 │ Второй символ имени (код ASCII) │ │ "Содержимое" этого ├──────┼───────────────────────────────────────┤ │ байта возвращает ───▶│ 0 │ Порядок и знак (знак в первом бите) │ │ функция VARPTR() │ │ (двоичная) │ │ │ │ │ │ Величина порядка = двоичному значению │ │ │ │ │ │ семи последних битов - 64 │ │ │ │ ├──────┼───────────────────────────────────────┤ │ │ │ │ 1÷7 │ М а н т и с с а (двоично-десятичная) │ └─────────┴───────┴──────┴──────┴───────────────────────────────────────┘ П р и м е р 5. Попробуйте самостоятельно в нем разобраться! ───────────── 10 INPUT"Введите число";A:PRINT"Попробуем 'собрать' его из памяти" 30 B=VARPTR(A):K$=RIGHT$("00000000"+BIN$(PEEK(B)),8) 50 IF MID$(K$,1,1)="1" THEN Z$="-." ELSE Z$="+." 60 FOR T=1 TO 7:Z$=Z$+RIGHT$("00"+HEX$(PEEK(B+T)),2):NEXT 70 Z$=Z$+"E" 80 IF MID$(K$,2,1)="1" THEN Z$=Z$+"+" ELSE Z$=Z$+"-" 90 U=VAL("&b"+MID$(K$,2,7))-64 100 C$=MID$(STR$(U),2):Z$=Z$+RIGHT$("00"+C$,2) 120 PRINT"Вот Ваше число:";Z$ run run Введите число? 0 Введите число? -23545e37 Попробуем 'собрать' его из памяти Попробуем 'собрать' его из памяти Вот Ваше число:+.00000000000000E-64 Вот Ваше число:-.23545000000000E+42 Ok Ok X.4.2. Х р а н е н и е э л е м е н т о в ч и с л о в ы х м а с с и в о в Что имеем - не храним; потерявши - плачем. Козьма Прутков Вначале мы расскажем Вам о том, как хранится в памяти ц е л о ч и с- л е н н ы й массив. Приведем схемы расположения информации в памяти для целочисленных чис- ловых массивов: α) о д н о м е р н ы й массив. Байт, адрес которого возвращает функция VARPTR()──┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─▼─┬───┬───┬───┬─ │ │ │ │ │ │ &H1 │ │ │███│███│███│███│··· └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───┴───┴───┴───┴─ X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 └──▲───┘└──▲───┘ ▲ ─────▲───── ────▲──── ▲ ────▲──── │ │ │ │ │ │ │ Значение Значение │ │ Служебная инф. │ │ нулевого первого │ Идентификатор Для одномерного │ элемента элемента Тип массива массива Количество элементов в массиве β) д в у х м е р н ы й массив. Байт, адрес которого возвращает функция VARPTR()──┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─▼─┬───┬─ │ │ │ │ │ │ &H2 │ │ │ │ │███│███│··· └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───┴───┴─ X-10 X-9 X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 └───▲───┘ ▲ ─────▲───── ─────▲───── ▲ ─────▲───── ─────▲───── │ │ │ │ │ │ │ Значение │ │ Служебная инф. │ Количество Количество элемента │ Идентификатор Для двухмерного столбцов строк (0,0) Тип массива массива γ) для э л е м е н т о в целочисленных числовых массивов. ┌─── Байт, адрес которого возвращает функция VARPTR() ┌─▼─┬───┬───┬───┐ │███│███│···│███│ └───┴───┴───┴───┘ └───────▲───────┘ │ Значение Не забыли ли Вы набрать и ввести в память компьютера оператор CLEAR? П р и м е р 4. DIM C5%(2):C5%(0)=32000:C5%(1)=13:C5%(2)=-4 ───────────── Ok print HEX$(VARPTR(C5%(0))-8) 8003 Ok ? РЕЕК(&Н8003) Этот адрес соответствует байту VALTYPE. Поэтому Вы долж- ны получить 2. ? РЕЕК(&Н8004) Этот адрес соответствует первому символу имени массива. Вы получите число 67, которое является кодом ASCII сим- вола "C". ? PEEK(&H8005) Этот адрес соответствует второму символу имени массива Вы получите число 53, которое является кодом ASCII символа "5". ? РЕЕК(&Н8006) 9 } Служебная информация ? РЕЕК(&Н8007) 0 ? PEEK(&H8008) Массив C5% - одномерный, поэтому мы получили 1. ? PEEK(&H8009) Мы получили 3 = 00000011 ? PEEK(&H800А) 0 = 00000000 ² ² Теперь можно найти количество элементов в массиве:00000000 00000011 =3 ² ? PEEK(&H800B) 0 = 00000000 ? PEEK(&H800C) 125 = 01111101 ² ² А тогда нулевой элемент массива равен: 01111101 00000000 = 32000 ² ? PEEK(&H800D) 13 = 00001101 ? PEEK(&H800E) 0 ² Добрались до первого элемента массива: 00000000 00001101 = 13 ² ? PEEK(&H800F) 252 = 11111100 ? PEEK(&H8010) 255 = 11111111 ² ² Далее 11111111 11111100 ──▶00000000 00000011 +1 ──▶00000000 00000100 =4 ² ² ² Приведем схемы расположения информации в памяти для нецелочисленных числовых массивов: α) о д н о м е р н ы й массив. Байт, адрес которого возвращает функция VARPTR() ──┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──▼──┬───┬───┬───┬── │ │ │ │ │ │ &H1 │ │ │ ∗∗∗ │███│···│███│··· └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───┴───┴───┴── X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 X └─────▲─────┘ ▲ ─────▲───── ────▲──── ▲ ────▲──── ▲ │ │ │ │ │ │ │ Значение нулевого │ │ Служебная инф. │ │ Знак и порядок элемента │ Идентификатор Для одномерного │ нулевого элемента Тип массива массива │ Количество элементов в массиве β) д в у х м е р н ы й массив. Байт, адрес которого возвращает функция VARPTR() ──┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──▼──┬─ │ │ │ │ │ │ &H2 │ │ │ │ │ ∗∗∗ │··· └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─ X-10 X-9 X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 X ▲ ─────▲───── ─────▲───── ▲ ─────▲───── ─────▲───── ▲ │ │ │ │ │ │ │ │ │ Служебная инф. │ Количество │ Знак и порядок │ Идентификатор Для двухмерного столбцов │ нулевого элем. Тип массива массива Количество строк γ) для э л е м е н т о в нецелочисленных числовых массивов. ┌─── Байт, адрес которого возвращает функция VARPTR() ┌─▼─┬───┬───┬───┬───┐ │∗∗∗│███│███│···│███│ └───┴───┴───┴───┴───┘ X └───────▲───────┘ ▲ │ │ Значение Знак и порядок Думаем, что теперь Вы в состоянии самостоятельно разобраться с вопроса- ми, касающимися "хранения" в RAM многомерных (двухмерных, трехмерных и т.д.) вещественных числовых массивов! X.5. С т е к А люди все роптали и роптали, А люди справедливости хотят: - Мы в очереди первые стояли, А те, кто сзади нас,- уже едят. В.Высоцкий С т е к (от англ. "stack" - "стог", "груда") - структура данных или устройство памяти для хранения наращиваемой и сокращаемой последователь- ности значений, в которой в любой момент доступен только последний член последовательности. Примером с т е к а является стопка книг на столе, в которой брать и класть книги можно только сверху ("Математический Энцик- лопедический Словарь"). С т е к используется как программой на MSX BASIC, так и подпрограмма- ми на машинном языке. "Вершина"стека указывается в слове STKTOP(&HF674). Его позиция зависит от размеров строкового пространства и блоков управления файлами, а также от второго аргумента оператора CLEAR (если этот оператор был выполнен). Если Вы хотите получить такие же результаты, как в последующем приме- ре, воспользуйтесь командой CLEAR! П р и м е р 1. Рассмотрим структуру расположения информации о цик- ───────────── ле FOR. α) 10 FOR AB%=2 TO 7 STEP 15 'Оператора NEXT быть не должно! run Ok PRINT HEX$(PEEK(&HF675))+HEX$(PEEK(&HF674)) F0A0 Ok Адрес значения AB% в VT Адреса байтов │ ┌─ FF◀── для отрицательного шага │ ┌────┬─────▼─────┬──▼─┬────┬─────┬─────┬─────┬─────┬─────┬─────┬───┬─ │ │&H82│ &H1B│ &H80│ &H1│&HFF│ ··· │ &HF │ &H0 │ &H7 │ &H0 │ &HA │&H0│ │ └────┴─────┴─────┴────┴────┴─────┴─────┴─────┴─────┴─────┴─────┴───┴─ └─▶ X-27 X-26 X-25 X-24 X-23 X-10 X-9 X-8 X-7 X-6 X-5 ▲ ────────── ▲ ▲ ─────▲──── ─────▲───── ────▲──── │ │ │ │ │ │ │ Знак шага │ Шаг (со знаком) Верхний Номер Код оператора FOR Тип параметра цикла предел строки ┌── Адрес данного байта &HF0A0 ─┬────┬─────┬───┬──▼──┐ │&H15│ &H80│···│ &HFF│ ─┴────┴─────┴───┴─────┘ X-4 X-3 X ─────▲───── ▲ │ └── "Вершина" стека Адрес конца программной строки Перед выполнением следующего примера наберите команду CLEAR! β) 10 FOR AB=2.7 TO 7 STEP-32.671533782376 run Ok PRINT HEX$(PEEK(&HF675))+HEX$(PEEK(&HF674)) F0A0 Ok Адрес параметра цикла AB в VT, увеличенный на 2 │ ┌─────┬────▼──────┬─────┬──────┬──────┬─ │ &H82│&H25│ &H80 │ &HFF│ &H5 │ &HC2 │ └─────┴────┴──────┴─────┴──────┴──────┴─ X-27 X-26 X-25 X-24 X-23 X-22 ◀── Адреса байтов ▲ ────────── ▲ ▲ ▲ │ │ │ └── Порядок и знак шага Код оператора FOR │ └───────── Тип параметра цикла └─────────────── Знак шага ─┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─ │ &H32│ &H67│ &H15│ &H33│ &H78│ &H23│ &H76│ &H41│ ─┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─ X-21 X-20 X-19 X-18 X-17 X-16 X-15 X-14 ────────────────────▲─────────────────── ▲ │ │ Мантисса шага Порядок и знак верхнего предела ┬──────┬────┬────┬────┬───┬───┬───┬─────┬───┬─────┬─────┬───┬────┐ │ &H70 │ &H0│ &H0│ &H0│&H0│&H0│&H0│ &HA │&H0│ &H1F│ &H80│···│&HFF│ ┴──────┴────┴────┴────┴───┴───┴───┴─────┴───┴─────┴─────┴───┴────┘ X-13 X-12 X-11 X-10 X-9 X-8 X-7 X-6 X-5 X-4 X-3 X ────────────────────▲───────────── ───▲─── ────▲──── │ │ │ Мантисса верхнего предела │ Адрес конца программной строки Номер строки оператора FOR Не забудьте о команде CLEAR! γ) 10 FOR AB!=2.7 TO 7 STEP-32.6715 run Ok PRINT HEX$(PEEK(&HF675))+HEX$(PEEK(&HF674)) F0A0 Ok Адрес параметра цикла AB! в VT, увеличенный на 2 │ ┌────┬────▼──────┬─────┬─────┬───┬────┬────┬─────┬─────┬─────┬─────┬─ │&H82│&H22│ &H80 │ &HFF│ &H1 │···│&HC2│&H32│ &H67│ &H15│ &H0 │ &H0 │ └────┴────┴──────┴─────┴─────┴───┴────┴────┴─────┴─────┴─────┴─────┴─ X-27 X-26 X-25 X-24 X-23 X-14 X-13 X-12 X-11 X-10 X-9 ▲ ────────── ─▲─ ─▲── ▲ ────────▲─────── ────▲──── │ │ │ │ │ │ Код FOR Знак шага │ Знак и порядок шага │ 3÷6-я цифры мантиссы │ Мантисса шага верхнего Тип параметра цикла предела ─┬─────┬─────┬─────┬─────┬─────┬─────┬───┬─────┐ │ &H41│ &H70│ &HA │ &H0 │ &H1C│ &H80│···│ &HFF│ ─┴─────┴─────┴─────┴─────┴─────┴─────┴───┴─────┘ X-8 X-7 X-6 X-5 X-4 X-3 X ▲ ─▲─ ─────▲───── ─────▲───── ▲ │ │ │ │ └─"Вершина" стека │ │ │ Адрес конца строки │ │ Номер строки │ 1÷2-я цифры мантиссы верхнего предела Знак и порядок верхнего предела Хотите получить те же результаты - пользуйтесь оператором CLEAR! δ) Пример под рубрикой: "Стек в действии!" 10 FOR AB%=2 TO 7:NEXT ◀── Цикл закрыт! 20 FOR I%=3 TO 9 ◀── Цикл не закрыт! run Ok PRINT HEX$(PEEK(&HF675))+HEX$(PEEK(&HF674)) F0A0 Ok Адрес текущего значения I% в VT Адреса байтов │ ┌─ FF◀── для отрицательного шага │ ┌────┬─────▼─────┬──▼─┬────┬─────┬─────┬─────┬─────┬─────┬─────┬───┬─ │ │&H82│ &H2C│ &H80│ &H1│&HFF│ ··· │ &H1 │ &H0 │ &H9 │ &H0 │ &H14│&H0│ │ └────┴─────┴─────┴────┴────┴─────┴─────┴─────┴─────┴─────┴─────┴───┴─ └─▶ X-27 X-26 X-25 X-24 X-23 X-10 X-9 X-8 X-7 X-6 X-5 ▲ ────────── ▲ ▲ ─────▲──── ─────▲───── ────▲──── │ │ │ │ │ │ Код оператора FOR Знак шага │ Шаг (со знаком) Верхний Номер Тип параметра цикла предел строки ┌── Адрес этого байта: &HF0A0 ─┬────┬─────┬───┬──▼──┐ │&H21│ &H80│···│ &HFF│ ─┴────┴─────┴───┴─────┘ X-4 X-3 X ─────▲───── ▲ │ └── "Вершина" стека Адрес конца программной строки Отметим, что для версии MSX-Disk BASIC с отключенным дисководом B при нулевой длине строковой области максимальное число вложенных циклов равно 576. А теперь настала очередь оператора GOSUB... Тем не менее о команде CLEAR забывать не стоит! П р и м е р 2. 10 GOSUB 30:INPUT A ───────────── 20 'Просто комментарий! 30 'Еще один комментарий! run Ok PRINT HEX$(PEEK(&HF675))+HEX$(PEEK(&HF674)) F0A0 Ok ┌── Адрес этого ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬──▼─┐ байта: &HF0A0 │&H8D│ &H0│ &H0│&H0A│ &H0│&h0A│&h80│ &H0│ &H0│&HFF│ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ X-9 X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 X ─▲─ ────▲──── ───▲──── ─▲─ │ │ │ └─ Вершина стека Код GOSUB │ Адрес следующего оператора Номер текущей строки П р и м е р 3. Работу этих двух программ Вы должны проверить на учени- ───────────── ческом компьютере. α) 10 GOSUB 30:PRINT 1:END β) 10 GOSUB 30:PRINT 1:END 20 PRINT 2:END 20 PRINT 2:END 30 'POKE (&HF0A0-4),&H10 30 POKE (&HF0A0-4),&H10'Адрес перехода 40 RETURN 40 RETURN 'изменен с адреса &H800A на run run 'адрес &H8010 1 2 Ok Ok X.6. Х р а н е н и е с т р о к о в ы х в е л и ч и н Функция VARPTR указывает адрес расположения строковых данных в опера- тивной памяти. Она имеет следующий синтаксис: ┌────────────────┐ │ VARPTR(γ) │ , └────────────────┘ где: VARPTR("VARiable PoinTeR"-"указатель переменной") - служебное слово; γ - идентификатор с т р о к о в о й переменной. Если переменная не существует, то выдается сообщение: "Illegal function call" . Функция VARPTR возвращает число X - а д р е с байта, находящегося на 3 позиции правее той, с которой располагается информация о переменной γ. Пусть γ - простая строковая переменная. Изобразим "кусочек" памяти в окрестности байта с адресом X: ┌── Байт, адрес которого возвращает функция ┌───┬───┬───┬─▼─┬───┬───┐ VARPTR(γ) │▧▧▧│▧▧▧│▧▧▧│∗∗∗│▧▧▧│▧▧▧│ └───┴───┴───┴───┴───┴───┘ Адреса──▶ X-3 X-2 X-1 X X+1 X+2 ▲ └───▲───┘ ▲ └───▲───┘ │ │ │ └─ Ссылка на адрес в PIT или на адрес Тип переменной │ └── Байт длины в строковой области Идентификатор Напомним Вам, что в программировании с с ы л к а - содержимое ячейки памяти, воспринимаемое как адрес некоторой другой ячейки. Указатели строковых переменных хранятся в VT. Они занимают 6 байтов, причем: α) один байт содержит "паспорт" переменной VALTYPE (число 3); β) два байта содержат имя строковой переменной; γ) один байт содержит длину строки, возвращаемую функцией VARPTR(γ)(та- ким образом, обе функции LEN(A$) и PEEK(VARPTR(A$)) возвращают одно и то же значение). Приведем простой пример: 10 A$="карандаш":PRINT LEN(A$);PEEK(VARPTR(A$)) run 8 8 Ok δ) следующие два байта указывают адрес первого байта строки. Если сим- вольная переменная создается явным присваиванием символьной константы, то указатель задает адрес этой константы в PIT.Лишь затем MSX-BASIC м о ж е т переслать значение этой символьной переменной в зарезервированное для нее строковое пространство. П р и м е р 1. "Сборка" значения строковой переменной A$ из памяти. ───────────── 5 CLEAR:INPUT A$:A$=A$+"":A=PEEK(VARPTR(A$)):PRINT A 30 B$="&H"+HEX$(PEEK(VARPTR(A$)+2))+HEX$(PEEK(VARPTR(A$)+1)):PRINT B$ 40 B=VAL(B$):BB=PEEK(B) 45 FOR I=0 TO A-1:PRINT CHR$(PEEK(B+I));:NEXT I:END run ? MSX 3 &HF163 MSX Ok П р и м е р 2. 10 A$="ABCD" ───────────── run Ok PRINT HEX$(VARPTR(A$)) 8014 ◀── Это адрес байта, содержащего длину A$ Ok Затем выполните команду PRINT PEEK(AD),где значение переменной AD изме- няется от &H8011 до &H8016. Вы получите: PRINT PEEK(&H8011) ──▶ 3 VALTYPE PRINT PEEK(&H8012) ──▶ 65 Код ASCII символа "A" PRINT PEEK(&H8013) ──▶ 0 Второй символ отсутствует PRINT PEEK(&H8014) ──▶ 4 Длина значения A$ PRINT PEEK(&H8015) ──▶ 9 = 0000 1001 ──▶ Адрес (младший байт) ² PRINT PEEK(&H8016) ──▶ 128 = 1000 0000 ──▶ Адрес (старший байт) ² 1000 0000 0000 1001 = &H8009 ² Итак, строка помещается в PIT по адресу &H8009.Все остальное очевидно! ? PEEK(&H8009) ? PEEK(&H800A) ? PEEK(&H800B) ? PEEK(&H800C) 65 66 67 68 ◀── Код ASCII "D" Ok Ok Ok Ok П р и м е р 3. А теперь измените строку 10 и выполните программу. ───────────── 10 A$="ABCD"+"" run Ok Повторите вышеуказанные шаги. Вы получите: PRINT HEX$(VARPTR(A$)) 8017 Ok PRINT PEEK(&H8014) ──▶ 3 VALTYPE PRINT PEEK(&H8015) ──▶ 65 Код ASCII для символа "А" PRINT PEEK(&H8016) ──▶ 0 В имени нет второго символа PRINT PEEK(&H8017) ──▶ 4 Длина строки PRINT PEEK(&H8018) ──▶ 101 = &H65 ──▶ Новый адрес (млaдший байт) PRINT PEEK(&H8019) ──▶ 241 = &HF1 ──▶ Новый адрес (старший байт) Операция, выполненная над строкой, явилась причиной пересылки ее по ад- ресу &HF165 (без изменения). Вы можете убедиться в этом, используя "в ок- рестности" этого адреса функцию PEEK. ? PEEK(&HF165) ? PEEK(&HF166) ? PEEK(&HF167) ? PEEK(&HF168) 65 66 67 68 Ok Ok Ok Ok Забегая несколько вперед, отметим, что функция FRE("") возвратила бы число 200 в первом случае, однако во втором случае возвращает число 196. Приведем схемы расположения информации в памяти для строковых массивов: α) о д н о м е р н ы й строковый массив. VARPTR(B$(0))───┐ ┌── VARPTR(B$(1)) ┌───┬───┬───┬───┬───┬───┬───┬───┬─▼─┬───┬───┬─▼─┬───┬───┬─── │ │ │ │ │ │ 1 │ │ │∗∗∗│ │ │∗∗∗│ │ │∗∗∗ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴─── X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 X X+1 X+2 X+3 X+4 X+5 ▲ ───▲─── ───▲─── ▲ ───▲─── ▲ ─▲───── ─▲─ ───▲─── │ │ │ │ │ │ │ │ │ Тип массива │ Служебная │ │ │ │ │ Адрес 1-го элемента Идентификатор информация│ │ │ │ Длина значения 1-го элемента │ │ │ Адрес 0-го элемента Размерность массива │ Длина значения 0-го элемента Число элементов в массиве П р и м е р 4. Обязательно разберите пример "с компьютером в руках"! ───────────── 10 DIM SM$(2):SM$(0)="рог"+"":SM$(1)="Inform"+"":SM$(2)="1989 г."+"" run Ok print HEX$(VARPTR(SM$(0))-8) 8047 Ok ? РЕЕК(&Н8047)◀── Этот адрес соответствует байту VALTYPE. Поэтому Вы должны получить 3. ? РЕЕК(&Н8048)◀── Этот адрес соответствует первому символу имени строкового массива. Вы должны получить число 83 - код ASCII символа "S". ? PEEK(&H8049)◀── Этот адрес соответствует второму символу имени строкового массива. Вы должны получить число 77 - код ASCII символа "М". ? РЕЕК(&Н804A)◀── 12 Служебная информация ? РЕЕК(&Н804B)◀── 0 Служебная информация ? PEEK(&H804C)◀── Массив SM$ одномерный, поэтому мы и получили 1. ? PEEK(&H804D)◀── 3 = 00000011 ? PEEK(&H804E)◀── 0 = 00000000 ² ² Теперь находим количество элементов в массиве: 00000000 00000011 = 3 ? PEEK(&H800F)◀── 3 Длина значения элемента SM$(0). ² Приведем схему расположения информации в памяти для элемента строково- го массива: ┌───┬───┬───┐ │∗∗∗│▧▧▧│▧▧▧│ └───┴───┴───┘ Адреса ──▶ X X+1 X+2 ▲ ───▲─── Байт длины ──┘ └── Ссылка на адрес в строковой области Продолжим наш пример: PRINT HEX$(VARPTR(SM$(0))) 804F Ok ? PEEK(&h804F) 3 ◀──── Длина значения 0-го элемента массива Ok ? HEX$(PEEK(&h8050)) 66 ◀──── Младший байт адреса 0-го элемента в стро- Ok ковой области ? HEX$(PEEK(&h8051)) F1 ◀──── Старший байт адреса 0-го элемента в стро- Ok ковой области ? CHR$(PEEK(&hF166)) р ◀─┐ Ok │ ? CHR$(PEEK(&hF167)) │ Символы о ◀─┤ значения Ok │ 0-го ? CHR$(PEEK(&hF168)) │ элемента г ◀─┘ Ok β) д в у х м е р н ы й строковый массив. Значение, возвращаемое функцией VARPTR() ───┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──▼──┐ │ │ │ │ │ │ &H2 │ │ │ │ │ ∗∗∗ │ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ X-10 X-9 X-8 X-7 X-6 X-5 X-4 X-3 X-2 X-1 X ▲ ─────▲───── ─────▲───── ▲ ─────▲───── ─────▲───── ▲ │ │ │ │ │ │ │ │ │ Служебная инф. │ │ │ Длина значения │ Идентификатор Для двухмерного │ │ нулевого элем. Тип массива массива │ │ Количество столбцов Количество строк X.7. О п е р а т о р CLEAR Чтобы вычистить одно, приходится выпачкать что-нибудь другое; но можно испачкать все, что угодно, и ничего при этом не вычистить. Принцип накопления грязи по Питеру Оператор CLEAR в общем виде записывается так: CLEAR [[n][,A]] , где: CLEAR ("очистить") - служебное слово; n - арифметическое выражение, целая часть значения которого ука- зывает количество байтов, резервируемых под строковое пространство; значе- ние параметра n меняется от нуля до размеров свободного пространства и равно 200 по умолчанию; А - арифметическое выражение, целая часть значения которого опре- деляет адрес первого байта участка памяти, расположенного между блоком уп- равления файлами и рабочей областью. Этот участок не будет "обработан" ин- терпретатором,поэтому он является как бы "резервной" памятью, где Вы може- те хранить, например, подпрограммы на машинном языке и другие необходимые Вам данные. Другими словами, значение А "переопределяет верхний предел пространст- ва, используемого MSX-BASIC". Например, команда CLEAR 1000,&HF000 отводит 1000 байтов для значений строковых констант в строковой области и RAM с адреса &HF000 для размеще- ния машинной подпрограммы. М а к с и м а л ь н ы м значением выражения A , конечно же, является адрес, с которого начинается рабочая область (&HF380). М и н и м а л ь н о е значение выражения A может быть определено по формуле: ┌────────────────────────────────┐ │ 267·(MAXFIL+1)+VARTAB+145+n+2 │ , └────────────────────────────────┘ где: MAXFIL - имя слова, расположенного в рабочей области по адресу &HF85F. Этот адрес занимает 1 байт памяти; VARTAB - имя слова, расположенного в рабочей области по адресу &HF6C2. Адрес занимает 2 байта памяти и отмечает конец PIT; n - размер строкового пространства в байтах. Заметим, что м и н и м а л ь н а я величина стека равна 145 байтам (это пространство между концом PIT и вершиной стека). Отметим, что оба аргумента могут быть опущены; в этом случае оператор CLEAR производит "чистку" значений всех числовых и строковых переменных, элементов массивов и определенных пользователем функций DEFFN, а также "уничтожает" стек. Сохранятся только текст программы на MSX-BASIC и ранее зарезервирован- ные машинные подпрограммы! Приведем два простеньких примера: 1) 10 DEF FNY(X)=X run ────▶ clear ───▶ print FNY(1) Ok Ok Undefined user function Ok 2) 10 M=9:T$=STRING$(5,"+"):?M;T$ 20 CLEAR:?M;T$ run 9 +++++ 0 Ok Параметр n в операторе CLEAR n указывает размер строковой области, зарезервированной для хранения строковых значений, причем ┌──────────────────────────────────┐ │ 0 ≤ n ≤ FRETOP-VARTAB-145-16 │ , └──────────────────────────────────┘ где: FRETOP - имя слова, расположенного в рабочей области по адресу &HF69B. Этот адрес занимает 2 байта памяти; VARTAB - имя слова, расположенного в рабочей области по адресу &HF6C2. Этот адрес занимает 2 байта памяти. П р и м е р 3. Нажмите кнопку "RESET" Вашего компьютера. ───────────── А теперь: print hex$(peek(&HF69C));" ";hex$(peek(&HF69B)) DC 5F ◀───── Вы узнали адрес FRETOP ? Ok print hex$(peek(&HF6C3));" ";hex$(peek(&HF6C2)) 80 3 ◀───── Вы узнали адрес VARTAB ? Ok print &HDC5F-&H8003-145-16 23483 ◀───── 0≤n≤23483 Ok П р и м е р 4. 10 CLEAR200:T$=SPACE$(198):B$=STRING$(2,"#"):?B$:X$= ───────────── STRING$(1,"L"):?X$ run ## Out of string space in 10 Ok Получили сообщение об отсутствии места в строковой области, так как 200 байтов, отведенных для нее по умолчанию, оказались уже исчерпанными. Однако... 10 CLEAR 200:T$=SPACE$(198):B$=STRING$(2,"#"):?B$;:CLEAR1:X$=STRING$(1," L"):? FRE("") run ## 0 Ok X.8. Ф у н к ц и я FRE Garbage collection ("чистка памяти","сборка мусора") - действия системы динамического распределения памяти для обнаружения неис- пользуемых программой блоков памяти и присоединения их к списку свободной памяти для повторного использования. Англо-русский словарь по программированию и информатике Информацию о размере свободной области ("Free Area") в RAM можно полу- чить с помощью функции FRE, обращение к которой имеет вид: FRE(A) , где: FRE ("FREe"-"свободный") - служебное слово; A - арифметическое или строковое выражение, причем для интерпретато- ра важным является лишь тип выражения, а не его значение. На практике применяется следующий синтаксис: FRE(0) или FRE("") . Функция FRE(0) возвращает количество байтов, оставленных для расшире- ния PIT, VT, стека, строковой области и блока управления файлами. П р и м е р 1. ───────────── 10 ? FRE(0):X=451:? FRE(0):Z#=7.5:? FRE(0) 20 Y!=555:? FRE(0):W%=111:? FRE(0) run 28739 28728 ◀──т.к. переменная Х по умолчанию - двойной точности, а следова- тельно, занимает в памяти 11 байтов; 28717 ◀──т.к. переменная Z - двойной точности (занимает в памяти так- же 11 байтов); 28710 ◀──т.к. переменная Y - одинарной точности (занимает в памяти 7 байтов); 28705 ◀──т.к. переменная W - целого типа (занимает в памяти 5 байтов). Ok Функция FRE(0) выдает сообщение: "Out of memory" ("Н е х в а т а е т п а м я т и") при достижении значения, меньшего 145 байтов, минимально допустимого для стека системы MSX-BASIC. Посмотрите (предварительно нажав кнопку "RESET"): CLEAR 28868:PRINT FRE(0) CLEAR 28869:PRINT FRE(0) 147 Out of memory Ok Ok Заметим,что слово VARTAB отличается от слова TXTTAB на 2 байта (при от- сутствии программы!), поэтому, добавив эти 2 байта к 145 байтам, необходи- мым для работы стека, получаем число 147! Функция FRE("") возвращает количество свободных байтов в строковом про- странстве. Например: print FRE("") X$="2²"+"3²":print FRE("") 200 196 Ok Ok Кроме того, функция FRE("") выполняет важное дополнительное действие. Оно связано с наличием в MSX-BASIC строк переменной длины, обработка кото- рых может привести к явлению "фрагментации памяти" (внутри строковой обла- сти появляются участки,содержащие неиспользуемую информацию -"м у с о р"). Поэтому, если в качестве аргумента функции FRE задано выражение строково- го типа, перед вычислением объема свободной памяти функция выполняет "с б о р к у м у с о р а", т.е. удаление всех неиспользуемых данных и освобождение занимаемых ими областей. П р и м е р 2. Оказывается,что если у Вас в начале программы встречает- ───────────── ся оператор A$="ABCD"+"EF", а затем оператор A$="X"+"Y", то Вы сразу же создадите 6-байтовое пространство, заполненное "мусором"! Покажем это: print HEX$(PEEK(&HF69C)); HEX$(PEEK(&HF69B)); F168 ────▲─── Ok └── Адрес "верхушки" строкового a$="ABCD"+"EF" пространства Ok for t=0 to 5:print chr$(peek(&HF168-t));:next FEDCBA Ok a$="X"+"Y" Ok for t=0 to 7:print chr$(peek(&hF168-t));:next FEDCBAYX └──▲─┘ Ok └─── "м у с о р" print fre("")'Избавимся от "мусора"! 198 Ok for t=0 to 7:print chr$(peek(&hF168-t));:next YXXCBAYX └──▲─┘ Ok └─── "м у с о р" Из примера следует, что строки хранятся в строковом пространстве в том порядке, в каком они были определены. Таким образом, функция FRE("") изменила положение значения строковой переменной (это и называется "с б о р к о й м у с о р а"). Если под строки зарезервирован большой объем строкового пространства и определено много символьных переменных, время "сборки мусора" может соста- вить несколько минут. При выполнении этой операции компьютер полностью "застывает". Посмотрите... П р и м е р 3. ───────────── 10 CLEAR 5000 'Объявлен размер строковой области - 5000 байтов 15 DEFINT A-Z:DIM A$(1500):FOR I=1 TO 1500:A$(I)="2"+"":NEXT 30 'Размещение данных в строковой области, "мусора" нет! 40 TIME=0:PRINT FRE(""),TIME/60/60"мин" run run ·3500··········3.61638888888 мин ·3500··········3.3716666666667 мин Ok (для MSX-1) Ok (для MSX-2) Интересно, что при изменении в строке 10 оператора CLEAR 5000 на опера- тор CLEAR 1600, результат получается почти тот же (≈3.607 мин. для компью- тера MSX-1 и ≈3.38 мин. для компьютера MSX-2)! Е д и н с т в е н н ы й способ уменьшить время "сборки мусора" - это использовать минимальное количество строк и особенно строковых массивов! Следует заметить, что некоторые строки хранятся в тексте самой програм- мы и, таким образом, не занимают места в строковой области. П р и м е р 4. ───────────── 10 ? FRE("");:U$="fywapro":D$="K":? FRE("");:DIM E$(150):? FRE("") 20 FOR K=1 TO 150:E$(K)=CHR$(K):NEXT:? FRE("") 30 E$(1)="APR":? FRE(""):E$(1)=" "+E$(1):? FRE("") run 200 200 200 Далее пауза для "сборки мусора"... 50 51 ◀── Произошла "сборка мусора" (свободное место в строковой облас- 47 ти увеличилось, т.к. значение элемента массива E$(1) уже хра- Ok нится в тексте программы)... Таким образом, строковая область является областью памяти, резервируе- мой для хранения строковых данных. Если Вы хотите зарезервировать в стро- ковом пространстве место для хранения 10 строк, содержащих каждая макси- мум 5 символов, то воспользуйтесь, например, оператором цикла: FOR I=1 TO 10:A$(I)=SPACE$(5):NEXT Во избежание "сборки мусора": α) определяйте все переменные в начале программы; β) используйте строковые функции MID$, LEFT$, RIGHT$. Перед работой со следующим примером выключите, а затем снова включите Ваш компьютер. П р и м е р 5. ───────────── A$="полет" Ok for t=0 to 10:print chr$(peek(&HF168-t));:next телопп██ Ok └▲─┘ └─────── "м у с о р" A$="налет" Ok for t=0 to 10:print chr$(peek(&HF168-t));:next телоптеланн └─▲─┘ ▲ └───────└──── "м у с о р" Ok print fre("") 195 Ok for t=0 to 10:print chr$(peek(&HF168-t));:next теланнеланн └─▲──┘ └─────── "м у с о р" Ok mid$(A$,1,2)="по" Ok for t=0 to 10:print chr$(peek(&HF168-t));:next телоппеланн └──▲─┘ └── "м у с о р" (он остался на прежнем месте!) Ok Отметим, что строковые функции MID$, LEFT$, RIGHT$ не изменяют указате- ли на значения строковых переменных. Обычный же оператор присваивания, ра- зумеется, указатели изменяет! Покажем это на примере (не забудьте о коман- де CLEAR !). П р и м е р 6. ───────────── a$="полет" Ok print hex$(peek(varptr(a$)+2)); hex$(peek(varptr(a$)+1)) F164 Ok a$="налет" Ok print hex$(peek(varptr(a$)+2)); hex$(peek(varptr(a$)+1)) F15F Ok а теперь... mid$(a$,1,2)="по" Ok print hex$(peek(varptr(a$)+2)); hex$(peek(varptr(a$)+1)) F15F ◀── Обратите внимание, это значение совпадает с предыдущим! Ok Как видим, значение указателя в последнем случае не изменилось! γ) при необходимости используйте оператор SWAP A$,В$ , который не меня- ет расположения значений переменных, а лишь меняет местами указатели на эти значения. Проиллюстрируем этот факт на примере... П р и м е р 7. ───────────── clear Ok print HEX$(PEEK(&HF69C)); HEX$(PEEK(&HF69B)) F168 Ok A$="Зачет":B$="Автомат" Оk for t=0 to len(a$)+len(b$):print chr$(peek(&hF168-t));:next течаЗтамотвАА Ok swap A$,B$ Ok for t=0 to len(a$)+len(b$):print chr$(peek(&hF168-t));:next течаЗтамотвАА Ok И наконец, функция FRE() может помочь Вам также в з а щ и т е Вашей программы. Например, в "укромном" месте программы, работающей со строко- вой информацией, поместите оператор X$=SPACE$(FRE(""))- конечно,Вы должны учесть, что целая часть значения аргумента функции SPACE$ должна принадле- жать отрезку [0,255]!. Это удержит "любознательных" от модификации значе- ний переменных Вашей программы (разумеется, в данном случае строковых)! Посмотрите: 10 X$=SPACE$(FRE("")):Y$="2"+Y$ run Out of string space in 10 Ok X.9. Р а б о ч а я о б л а с т ь В рабочей области содержатся системные подпрограммы, системные перемен- ные и "ловушки", используемые интерпретатором во время выполнения операто- ров Вашей программы. В рабочей области хранятся данные о позиции курсора, цвете текста, состоянии функциональных клавиш и другая полезная информа- ция, инициализируемая при включении компьютера. ┌─────────────────────────────────────────────────────────────────┐ │ Адрес, отмечающий н а ч а л о рабочей области, указан в │ │ самой этой области в слове HIMEM, содержимое которого за- │ │ нимает 2 байта, расположенных с адреса &HFC4A . │ └─────────────────────────────────────────────────────────────────┘ Еще раз напомним Вам, что адреса, занимающие два байта, всегда записы- ваются так: вначале записывается содержимое младшего байта, а затем содер- жимое старшего байта! Отметим, что значением выражения HEX$(PEEK(&HFC4A)+256*PEEK(&HFC4B)) является а д р е с н а ч а л а р а б о ч е й области. Поскольку рабочая область расположена в RAM, ее переменные могут изме- няться операторами POKE. Но это следует делать только в том случае, если Вы з н а е т е, что за этим последует! X.9.1. М а т р и ц а к л а в и а т у р ы М а т р и ц е й клавиатуры для MSX-компьютеров назовем таблицу вида: 0-й 1-й 2-й 3-й 4-й 5-й 6-й 7-й бит бит бит бит бит бит бит бит ┌──────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬──────┐ │Адреса│ &hFE│ &hFD│ &hFB│ &hF7│ &hEF│ &hDF│ &hBF │ &h7F │ │байтов│(254)│(253)│(251)│(247)│(239)│(223)│ (191)│ (127)│ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 0-я строка │ FBE5 │ 9 │ ; │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 1-я строка │ FBE6 │ 7 │ 8 │ 0 │ = │ - │ H │ : │ V │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 2-я строка │ FBE7 │ \ │ . │ B │ @ │ , │ / │ F │ I │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 3-я строка │ FBE8 │ S │ W │ U │ A │ P │ R │ [ │ O │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 4-я строка │ FBE9 │ L │ D │ X │ T │ ] │ Z │ J │ K │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 5-я строка │ FBEA │ Y │ E │ G │ M │ C │ ~ │ N │ Q │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 6-я строка │ FBEB │SHIFT│ CTRL│GRAPH│ CAPS│ РУС │ F1 │ F2 │ F3 │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ │ │ │ │ │ │ │ │ │ │ 7-я строка │ FBEC │ F4 │ F5 │ ESC │ TAB │ STOP│ BS │SELECT│RETURN│ │ │ │ │ │ │ │ │ │ │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ │ │ │ │ │ │ │ ▲ │ │ │ 8-я строка │ FBED │SPACE│ HOME│ INS │ DEL │◀────│ │ │ │ │ ───▶ │ │ │ │ │ │ │ │ │ ▼ │ │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 9-я строка │ FBEE │ RET │ + │ * │ 0 │ 1 │ 2 │ 3 │ 4 │ ├──────┼─────┼─────┼─────┼─────┼─────┼─────┼──────┼──────┤ 10-я строка │ FBEF │ 5 │ 6 │ 7 │ 8 │ 9 │ - │ , │ . │ └──────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴──────┘ Последние две строки соответствуют цифровой (правой) зоне клавиатуры учительского компьютера серии MSX-2. Ответим теперь на Ваш очевидный вопрос: ┌───────────────────────────────────────┐ │ Как воспользоваться этой таблицей? │ └───────────────────────────────────────┘ П р и м е р 1. Ниже приведены программа, останавливаемая нажатием кла- ───────────── виши "GRAPH": 10 Z=PEEK(&HFBEB):IF Z<>251 THEN 10 и программа, останавливаемая нажатием клавиш "SHIFT"+"CTRL": 10 Z=PEEK(&HFBEB):IF Z<>(254 AND 253) THEN 10 А теперь ответ на Ваш следующий вопрос: ┌─────────────────────────────────────────┐ │ А как получить матрицу клавиатуры ? │ └─────────────────────────────────────────┘ Для "чтения" нажатой клавиши достаточно "прочесть"слово NEWKEY (11 бай- тов) по адресу &HFBE5 из таблицы системных переменных. П р и м е р 2. Программа "пробегает" все клавиши и возвращает позицию ───────────── нажатой клавиши (X,Y) матрицы клавиатуры. 11 значений, записанных в слове NEWKEY, соответствуют 11 строкам матрицы клавиатуры. Если не нажата ни одна клавиша, содержанием каждого из 8 байтов, соответ- ствующих строке матрицы является 1. Это фиксируется двоичным числом &B11111111=255. Когда же клавиша нажата, считанное на этой строке значе- ние отличается от 255: бит соответствующей колонки "сбрасывается" в 0. 10 FOR Y=0 TO 10:Z=PEEK(&HFBE5+Y) 30 IF Z=255 THEN 80 '──▶ 40 PRINT"Y=";Y:Z$=RIGHT$("00000000"+BIN$(Z),8) 60 PRINT"X=";8-INSTR(Z$,"0"):PRINT 80 NEXT:GOTO 10 '──▶ X.9.2. Д и н а м и ч е с к а я к л а в и а т у р а [46] Промедление с легким делом превращает его в трудное, промедление же с трудным делом пре- вращает его в невозможное. Д.Лоример Исследуем один подход к разработке учебных программ, работающих под управлением интерпретатора MSX-BASIC. Существенная особенность этого под- хода состоит в том, что программа в процессе выполнения модифицируется (происходит изменение отдельных строк BASIC-программы или добавление но- вых строк). Считается,что допущение самомодификации программы во время вы- полнения является признаком плохого стиля программирования,поэтому начнем с примера, который показывает, что предлагаемый подход является не только оправданным, но и в ряде случаев единственно возможным. Пусть необходимо табулировать функцию y=f(x), конкретно x², то есть для каждого значения аргумента вычислить значение функции и результат за- писать в таблицу. Соответствующая программа выглядит следующим образом: 10 'Программа табулирования функции. 20 DIM X(200),Y(200) 30 INPUT XN,XK 'Задание границ изменения аргумента функции. ··· 100 GOSUB 1000 'Обращение к подпрограмме табулирования. ··· 999 END 1000 FOR I=1 TO 200:Y(I)=X(I)*X(I):NEXT I:RETURN Части программы между строками 30 и 100, 100 и 999 содержат операторы, обеспечивающие масштабирование, заполнение таблицы, защиту от ошибочных действий пользователя и т.д. Простота программы обусловлена тем, что зада- ча табулирования решается для ф и к с и р о в а н н о й функции. Попыта- емся теперь разработать программу, которая позволяет табулировать любую функцию одной переменной, аналитическое выражение которой вводится с кла- виатуры! Для реализации на ПЭВМ"YAMAHA" используем специальный механизм, введен- ный Дж.Баттерфилдом (J.Batterfield) и названный им принципом "д и н а м и- ч е с к о й к л а в и а т у р ы". В к о м а н д н о м (!) режиме информация,набираемая пользователем на клавиатуре, аппаратно записывается в буфер (называемый в дальнейшем б у- ф е р о м к л а в и а т у р ы , БК). БК размещается в рабочей области с адреса &HFBF0 по адрес &HFC17 и занимает 40 байтов. При нажатии клавиши "RETURN" содержимое БК считывается интерпретатором и выполняется соответ- ствующая команда. Имитация действий пользователя на основе принципа "динамической клавиа- туры" осуществляется следующим образом: 1) с помощью оператора INPUT вводится текст запроса (в данном случае - аналитическое выражение табулируемой функции) в символьную строку F$ (в приведенной ниже программе - строка 10); 2) символьная строка дополняется впереди номером, а в конце - кодом ко- манды "RETURN" (строки 15 и 1890): F$="номер строки 1"+F$+CHR$(13) ; 3) строка побайтно переписывается в БК, начиная с aдреса &HFBF0, при помощи оператора POKE и функции PEEK (подпрограмма, начинающаяся со стро- ки 1880); 4) строка S$="goto"+"номер строки 2"+CHR$(13), где н о м е р с т р о- к и 2 - номер строки программы, куда после модификации необходимо пере- дать управление, побайтно переписывается в БК (строка 25); 5) выполнение программы прекращается командой END, в результате проис- ходит переход из программного режима в командный. Интерпретатор считывает содержимое БК до первого появления CHR$(13) и выполняет его как команду, то есть модифицирует строку с номером н о м е р с т р о к и 1. Далее считывается остаток содержимого БК до второго появления CHR$(13), и он также выполняется интерпретатором, как команда, после чего происходит пе- реход в программный режим с передачей управления в строку с номером н о- м е р с т р о к и 2. Таким образом, указанный алгоритм решает задачу автоматической модифи- кации программы в соответствии с текстом запроса, вводимого пользователем с клавиатуры, и запуска ее с указанного номера строки. П р и м е р. ─────────── 1 GOTO 10 2 GOTO 30 10 LINEINPUT"Введите аналитическую запись функции:";F$ 11 INPUT"Укажите номер строки, содержащей оператор описания функции по льзователя DEFFN (51< номер строки <59)";SN:GOSUB 2410 13 GOSUB 1550 'Сохранение F$ 15 F$=STR$(SN)+F$:F$=MID$(F$,2,LEN(F$)-1) 20 GOSUB 1880 25 F$="goto2":GOSUB 1880:END 30 GOSUB 1730 'Восстановление F$ 50 '∗∗ Программа табулирования функции Y(x) ∗∗ 60 INPUT"Введите[A,B] и шаг табулирования H";A,B,H 65 FOR X=A TO B STEP H:PRINT X;FNY(X):NEXT 90 END 1550 '∗∗∗∗∗ Ф о р м и р о в а н и е F$ ∗∗∗∗∗ 1590 F$="deffny(x)="+F$:POKE &HF600,LEN(F$) 1620 FOR I=1 TO LEN(F$):POKE &HF601+I,ASC(MID$(F$,I,1)):NEXT 1720 RETURN'──▶ 1730 '∗∗∗∗∗ В о с с т а н о в л е н и е F$ ∗∗∗∗∗ 1740 LF=PEEK(&HF600):F$="" 1750 FOR I=1 TO LF:C=PEEK(&HF601+I):F$=F$+CHR$(C):NEXT 1780 RETURN'──▶ 1880 '∗∗∗∗∗ Д и н а м и ч е с к а я к л а в и а т у р а ∗∗∗∗∗ 1890 F$=F$+CHR$(13) 1900 AD=PEEK(&HF3F9)*256+PEEK(&HF3F8)-65536! 1910 L1=&HFC17-AD+1 1920 IF LEN(F$)<=L1 THEN GOTO 1990 1930 L2=LEN(F$)-L1:N=0 1940 FOR I=AD TO &HFC17:N=N+1 1950 POKE I,ASC(MID$(F$,N,1)):NEXT 1960 FOR I=&HFBF0 TO &HFBF0+L2-1:N=N+1 1970 POKE I,ASC(MID$(F$,N,1)):NEXT 1980 AD=&HFBF0+L2+65536!:POKE&HF3F9,FIX(AD/256):POKE&HF3F8,AD-FIX(AD/2 56)*256:GOTO 2050 1990 N=0 2000 FOR I=AD TO AD+LEN(F$)-1:N=N+1 2010 POKE I,ASC(MID$(F$,N,1)):NEXT 2020 IF LEN(F$)19 THEN CLS:LOCATE 1,10:PRINT"Эта программа имеет огра ничение:":GOTO 2420 ELSE GOTO 2460 2420 PRINT:PRINT "Длина формулы не должна превосходить 19 символов!";L EN(F$) 2440 PRINT:LOCATE 1,23:PRINT"Для продолжения нажмите любую клавишу" 2450 W$=INKEY$:IF W$="" THEN 2450 ELSE GOTO 10 2460 RETURN'──▶ X.10. П о р т ы в в о д а - в ы в о д а И я надеюсь, что наши потомки будут благодарны мне не только за то, что я здесь разъяснил, но и за то, что мною было добровольно опущено с целью предоставить им удовольствие самим найти это. P.Декарт. Геометрия П о р т ввода-вывода - многоразрядный вход или выход компьютера,через который процессор обменивается данными с внешними устройствами (клавиату- рой, принтером, дисководом, видеопамятью и видеопроцессором, игровыми ма- нипуляторами). Часто говорят, что порты представляют собой "интерфейсные схемы компьютера". Порт ввода-вывода напоминает морской порт, через который ввозят и выво- зят товары. В нашем случае через порты вводятся и выводятся данные. Порты принимают данные от периферийных устройств и направляют их в эти устройст- ва. Используя прямой доступ к портам ввода-вывода,Вы более полно использу- ете возможности компьютера. Процессор "работает" с портами по адресам, которые не следует путать с адресами ROM или RAM: 1) порты с адресами &H00÷&H7F. Вы не можете и з м е н и т ь их содер- жимоe (сравните с ROM!); 2) порты с адресами &H80÷&HFF. Их содержимое изменять можно (сравните с RAM!). Некоторые порты, их функции и адреса перечислены ниже: ┌────────────────┬───────────────┬─────────────────────────────────────┐ │ А д р е с │ Чтение(Запись)│ Н а з н а ч е н и е │ │────────────────┴───────────────┴─────────────────────────────────────┤ │ Порты, отвечающие за работу с локальной сетью КУВТ YAMAHA MSX-1 │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &H00 │ Чтение(Запись)│ Посылаемые данные │ │ &H01 │ Чтение │ Статус │ ├────────────────┼───────────────┼─────────────────────────────────────┤ │ &H02 │ Чтение │ Номер компьютера в локальной сети │ │ │ │ (только для компьютеров MSX-1) │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ Порты, отвечающие за работу с локальной сетью КУВТ YAMAHA MSX2 │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &H09 │ │ Командный порт (передача или прием) │ │ &H0C │ │ Порт состояния │ │ &H0E │ │ Порт данных │ ├─ ─ ─ ─ ─ ─ ─ ─ ┼─ ─ ─ ─ ─ ─ ─ ─┼─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┤ │ &H0A │ │ Используются при │ │ &H0B │ │ инициализации │ │ &H0D │ │ сетевого ОЗУ │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ П р и н т е р │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &H90 │ Чтение │ Ввод сигнала занятости принтера │ │ &H91 │ Запись │ Kод выводимого символа │ │ &H92 │ Запись │ ? │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ В и д е о п р о ц е с с о р (VDP) │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &H98 │ Чтение(Запись)│ Обращение к видеопамяти │ │ &H99 │ Чтение(Запись)│ Чтение (запись) в регистр VDP │ │ &H9A │ Запись │ Запись в регистр палитры │ │ &H9B │ Запись │ Косвенная запись в регистры VDP │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ З в у к о г е н е р а т о р (PSG) │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &HA0 │ Запись │ Ввод в порт номера регистра │ │ &HA1 │ Запись │ Ввод в порт информации для установ- │ │ │ │ ленного регистра │ │ &HA2 │ Чтение │ Чтение числа из регистра PSG │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ Программируемый п а р а л л е л ь н ы й и н т е р ф е й с (PPI) │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &HA8 │ Чтение(Запись)│ Запись (чтение) данных в порт A │ │ &HA9 │ Чтение(Запись)│ Запись (чтение) данных в порт B │ │ &HAA │ Чтение(Запись)│ Запись (чтение) данных в порт C │ │ &HAB │ Чтение(Запись)│ Запись (чтение) режимов PPI │ ├────────────────┼───────────────┼─────────────────────────────────────┤ │ &HB0 │ │ Внешняя память (SONY) (через &HB3) │ ├────────────────┴───────────────┴─────────────────────────────────────┤ │ Порты, отвечающие за работу с т а й м е р о м │ ├────────────────┬───────────────┬─────────────────────────────────────┤ │ &HB4 │ Запись │ Номер столбца ОЗУ таймера │ │ &HB5 │ Чтение(Запись)│ Чтение/запись данных в ОЗУ таймера │ ├────────────────┼───────────────┼─────────────────────────────────────┤ │ &HB8 │ │ Световое перо (Sanyo) (через &HBB) │ ├────────────────┼───────────────┼─────────────────────────────────────┤ │ │ │ Управление Audio/Video │ │ │ ├─────┬───────────────────────────────┤ │ │ │Номер│ Назначение бита │ │ │ │бита │ │ │ &HF7 │ Запись ├─────┼───────────────────────────────┤ │ │ │ 4 │ Управление AV (0 - TV) │ │ │ │ 5 │ Управление Ym (0 - TV) │ │ │ │ 6 │ Управление Ys (0 - Super) │ │ │ │ 7 │ Выбор Video (0 - TV) │ ├────────────────┼───────────────┼─────┴───────────────────────────────┤ │ &HFC │ Чтение(Запись)│ Регистры распределения │ │ &HFD │ Чтение(Запись)│ с л о т о в │ │ &HFE │ Чтение(Запись)│ (расширений памяти) │ │ &HFF │ Чтение(Запись)│ для компьютеров серии MSX-2 │ └────────────────┴───────────────┴─────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Для работы с портами ввода-вывода используются: │ │ функция INP и операторы OUT и WAIT │ └─────────────────────────────────────────────────────────┘ Формат оператора OUT: ┌──────────────────────────────────┐ │ OUT а д р е с, д а н н о е │, └──────────────────────────────────┘ где: OUT ("OUTput"-"вывод") - служебное слово; а д р е с - арифметическое выражение, целая часть значения которого принадлежит отрезку [128,255] (128=&H80, 255=&HFF); д а н н о е - арифметическое выражение, целая часть значения которо- го принадлежит отрезку [0,255]. Оператор OUT "посылает" заданное операндом д а н н о е значение в порт, номер которого задан значением параметра а д р е с . В н и м а н и е ! На компьютерах серии MSX-2 прежде, чем использовать опе- ратор OUT, необходимо в непосредственном режиме выполнить команду CALL NETEND (т.е. отключить Ваш компьютер от локальной сети). Опишем синтаксис функции INP: ┌────────────────────────┐ │ INP (а д р е с) │, └────────────────────────┘ где: INP ("INPut"-"ввод") - служебное слово; а д р е с - арифметическое выражение, целая часть значения которого принадлежит отрезку [0,255]. Функция INP возвращает целочисленное значение, "прочитанное" из порта, имеющего указанный адрес. Видна ли Вам аналогия между операторами POKE и OUT , PEEK и INP ?! При помощи функции INP Вы можете использовать в своих расчетах номер Вашего компьютера. Чтобы поместить в переменную А номер компьютера, на ко- тором Вы работаете в локальной сети MSX-1, примените оператор: ┌────────────────────┐ │ A=INP(&H02) AND 15 │. └────────────────────┘ Объясним роль логической операции AND. Значением, возвращаемым функци- ей INP(&H02), является двоичное число, записанное в одном байте. "Содержи- мое" четырех старших битов байта нас не интересует. Заметим, что число 15 = &b00001111. Как Вы уже, наверное, догадались, логическая операция AND позволяет выделить нужные нам четыре младших бита. X.10.1. П р о г р а м м и р у е м ы й п а р а л л е л ь н ы й и н т е р ф е й с (PPI) Теперь мы перейдем к рассказу о работе с портами Параллельного Програм- мируемого Интерфейса (PPI - "Parallel Programming Interface"). Напомним Вам, что и н т е р ф е й с (англ. "interface"-"сопряжение" - способ и средства установления и поддержания информационного обмена между исполнительными устройствами автоматической или человеко-машинной системы. В п а р а л л е л ь н о м интерфейсе порция двоичной информации, со- стоящая из n битов, передается одновременно по n каналам. ┌───────────────────────────────────────────────────────────────┐ 1. │ Порт А используется для выбора с л о т о в, осуществляющих │ │ управление расширенной памятью компьютера. │ └───────────────────────────────────────────────────────────────┘ За подробностями мы отсылаем Вас к Приложению 1 (раздел 1.8.2). ┌───────────────────────────────────────────────────────────────┐ │ Порты B и C применяются для "работы" с матрицей клавиатуры, │ │ причем номер строки матрицы клавиатуры "посылается" в порт │ │ порт C, а номер столбца "читается" в порту B . │ └───────────────────────────────────────────────────────────────┘ П р и м е р 1. О б н а р у ж е н и е нажатия клавиши "GRAPH". ───────────── Отметим, что клавиша "GRAPH" находится в строке 6 и столбце 2 матрицы клавиатуры (и строки и столбцы матрицы нумеруются, на- чиная с 0). Тогда: 1) номер строки матрицы клавиатуры "посылаем" в порт C : OUT &HAA,6 2) "извлекаем" номер столбца из порта B: X=INP(&HA9) До нажатия клавиш значением Х является число 255 = &b11111111.В момент нажатия какой-либо клавиши соответствующий бит порта B (в нашем случае - второй) на мгновение обнуляется. Таким образом, нажатие клавиши "GRAPH" легко обнаружить, если выделить значение интересующего нас бита командой: IF (X AND &b00000100)=0 THEN PRINT"GRAPH" Программа, позволяющая обнаружить нажатие клавиши "GRAPH",выглядит так: 10 OUT &HAA,6:X=INP(&HA9) 20 IF (X AND 4)=0 THEN PRINT "GRAPH":END 30 GOTO 10 Надеемся, что Вы обратили внимание на недостаток этой программы: после ее запуска неожиданно включается индикатор "CAPS" (но это не означает,что Вам удалось смоделировать нажатие клавиши "CAPS"!). Разберемся, почему так происходит. Взгляните на приведенную ниже таблицу, в которой описаны назначения би- тов порта C: ┌──────────┬──────────────────────────────────────────┐ │ Биты 0÷3 │ Строка матрицы клавиатуры │ │ Бит 4 │ Если 0, то запускается магнитная лента │ │ Бит 5 │ Сигнал записи на магнитную ленту │ │ Бит 6 │ Если 0, то включается индикатор "CAPS" │ │ Бит 7 │ Управление звуковым сигналом │ └──────────┴──────────────────────────────────────────┘ Все ясно! Индикатор "CAPS" включается потому, что в порт C записывает- ся значение 6 = &b00000110, а значит, шестой бит порта C "опрокинулся" в нуль. ▲ ─────┬──── └───────────────────────┘ Фактически только четыре м л а д ш и х бита порта C определяют номер строки матрицы клавиатуры. Для "маскирования" (игнорирования) значений че- тырех старших битов достаточно вместо команды OUT &HAA,6 выполнить ко- манду: OUT &HAA,6 OR (INP(&HAA) AND &HF0) │ │ │ ┌── !!! ▼ ▼ ▼ ▼ &B00000110 OR (&B∗1∗∗∗∗∗∗ AND &B11110000) = &B∗1∗∗ 0110 (символом "∗" отмечены биты, состояние которых в данном случае роли не иг- рает). П р и м е р 2. Включение индикатора "CAPS" (если он выключен!) можно ───────────── осуществить следующей командой: OUT &HAA, INP(&HAA) XOR &B01000000 │ │ ┌─ !!! ▼ ▼ ▼ &B∗1∗∗∗∗∗∗ XOR &B01000000 = &B∗0∗∗∗∗∗∗ П р и м е р 3. ───────────── 10 OUT &HAA,6 OR (INP(&HAA) AND &HF0):B=INP(&HA9) 20 IF (B AND 1)=0 THEN PRINT "Нажата клавиша SHIFT" 30 IF (B AND 2)=0 THEN PRINT "Нажата клавиша CTRL" 40 IF (B AND 4)=0 THEN PRINT "Нажата клавиша GRAPH":GOTO 10 ELSE 10 В ходе работы программы индикатор "CAPS" сохранит состояние, в котором он находился до пуска программы! П р и м е р 4. Получим матрицу клавиатуры при помощи оператора OUT! ───────────── ┌────────────────────────────────────────────┐ │ 10 INPUT "Номер строки";N │ │ 20 INPUT "Номер столбца";T │ │ 30 OUT &HAA,INP(&HAA) AND &HF0 OR N │ │ 40 B=((INP(&HA9) AND 2^T)=0) │ │ 50 IF B THEN PRINT "Клавиша нажата":END │ │ 60 GOTO 30 │ └────────────────────────────────────────────┘ П р и м е р 5. Включение и выключение кассетной ленты. Операторы MOTOR ───────────── ON и MOTOR OFF могут быть имитированы с помощью команды: OUT &HAA, INP(&HAA) XOR &B00010000 X.10.2. П р о г р а м м и р у е м ы й з в у к о в о й г е н е р а т о р (PSG) Вначале приведем два примера записи информации в PSG при помощи портов ввода-вывода. П р и м е р 6. ───────────── 10 SOUND 7,8 'Шум из канала A 10 OUT &HA0,7:OUT &HA1,8: A=INP(&HA2) 20 SOUND 8,15 'Громкость 20 OUT &HA0,8:OUT &HA1,15:B=INP(&HA2) 30 SOUND 6,26 'Частота звука 30 OUT &HA0,6:OUT &HA1,26:C=INP(&HA2) 40 END 40 PRINT A;B;C run 8 15 26 Ok П р и м е р 7. Представьте, что Вы находитесь на берегу Черного моря ───────────── в районе Ялты. Закройте глаза и ... 10 FOR I=0 TO 13:READ V 20 OUT &HA0,I:OUT &HA1,V 'Имитация действия оператора SOUND I,V 30 NEXT:END 100 DATA 0,0,0,0,0,0,30,&HB7,16,0,0,0,90,14 А теперь мы расскажем Вам, как можно "музицировать" при помощи н е п о- с р е д с т в е н н о й записи в регистры звукового генератора. Взгляни- те на следующую небольшую табличку: ┌─────────────┬───────────────────┬──────────────────┐ │ Исполняемая │ Содержимое │ Содержимое │ │ нота │ нулевого регистра │ первого регистра │ ├─────────────┼───────────────────┼──────────────────┤ │ PLAY "O4C" │ 172 │ 1 │ │ PLAY "O4C#" │ 148 │ 1 │ │ PLAY "O4D" │ 125 │ 1 │ │ PLAY "O4D#" │ 104 │ 1 │ │ PLAY "O4E" │ 83 │ 1 │ │ PLAY "O4F │ 64 │ 1 │ │ PLAY "O4F#" │ 46 │ 1 │ │ PLAY "O4G" │ 29 │ 1 │ │ PLAY "O4G#" │ 13 │ 1 │ │ PLAY "O4A" │ 254 │ 0 │ │ PLAY "O4A#" │ 240 │ 0 │ │ PLAY "O4B" │ 227 │ 0 │ │ PLAY "O5C" │ 214 │ 0 │ └─────────────┴───────────────────┴──────────────────┘ Вы можете проверить эту таблицу при помощи следующей программы: 10 PLAY "O4C" 20 OUT &hA0,0: PRINT INP(&hA2) 'Читаем данные 30 OUT &hA0,1: PRINT INP(&hA2) 'из регистров PSG А теперь слушайте ... П р и м е р 8. Гамма "до-мажор". ───────────── 10 DATA 1,172,1,125,1,83,1,64,1,29,0,254,0,227,0,214 20 OUT &HA0,8:OUT &HA1,15 'Установим громкость канала A 30 FOR T=1 TO 8:READ I1,I2 40 OUT &HA0,1:OUT &HA1,I1:OUT &HA0,0:OUT &HA1,I2 45 FOR K=1 TO 100:NEXT 'Если Вам захочется "озвучить", например, "выст рел", достаточно убрать из программы эту строку 50 NEXT 60 OUT &HA0,8:OUT &HA1,0 'Сбросим громкость канала A X.10.3. Д р у г и е п о р т ы. О п е р а т о р WAIT Приведем примеры использования "содержимого" других портов. П р и м е р 1. Использование портов с адресами &H90 и &H91 для вывода ───────────── символа на принтер. Вначале о "роли" первого бита порта с номером &H90: 7-й бит 6-й бит 5-й бит 4-й бит 3-й бит 2-й бит 1-й бит 0-й бит ┌───────┬────────┬────────┬────────┬───────┬───────┬───────┬───────┐ │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ │ ∗ │ └───────┴────────┴────────┴────────└───────┴───────┴───▲───┴───────┘ Принтер в режиме ON LINE (подключен к ПЭВМ): 0 ───────│ Принтер в режиме OFF LINE (отключен от ПЭВМ): 1 ───────┘ A теперь: включите принтер и вставьте бумагу... 5 CLEAR 300 10 INPUT"Слово";A$ 15 WAIT &H90,2,255 'Вы включите, наконец, принтер или нет? 20 A$=A$+CHR$(13)+CHR$(10) 'CHR$(13) - код возврата каретки; CHR$(10) - код перевода строки 30 FOR I=1 TO LEN(A$) 35 OUT &H90,0:OUT &H90,137 'Инициализация принтера 40 B=ASC(MID$(A$,I,1)) 50 OUT &H91,B 60 NEXT I П р и м е р 2. Считывание кода выведенного на экран символа: ───────────── 10 K$=INKEY$:IF K$="" THEN 10 ELSE PRINT K$; 20 PRINT INP(&H98):GOTO 10 Оператор WAIT используется сравнительно редко. Его синтаксис: ┌────────────────────────┐ │ WAIT P,M[,C] │, └────────────────────────┘ где: WAIT ("ожидать") - служебное слово; P - арифметическое выражение, целая часть значения которого задает адрес порта; M и C - арифметические выражения, целые части значений которых при- надлежат отрезку [0,255]. Оператор WAIT "заставляет" компьютер "ожидать", пока результатом "опро- са" порта с указанным адресом не окажется число 0 (данный порт "работает" в режиме ч т е н и я ). Другими словами, этот оператор является "беско- нечным циклом", который "ждет", пока от порта ввода-вывода не придет опре- деленный сигнал. Вы можете прервать затянувшееся "ожидание", нажав клавиши "CTRL+STOP" (при этом Вы вернетесь в командный режим). Содержимое порта с указанным адресом заносится в некоторый регистр про- цессора Z-80, который мы назовем X. Далее содержимое регистра X комбиниру- ется со значениями параметров M и С по формуле: X = (X XOR C) AND M Если после этого содержимое регистра X окажется равным 0, то происхо- дит "выход из оператора WAIT". В противном случае порт вновь "опрашивает- ся", и процесс повторяется. Приведем таблицу-"подсказку": ┌─────────────────┬───────────────────────────────┐ │ X │ 0 0 0 0 1 1 1 1 │ │ C │ 0 0 1 1 0 0 1 1 │ ├─────────────────┼───────────────────────────────┤ │ X XOR C │ 0 0 1 1 1 1 0 0 │ │ M │ 0 1 0 1 0 1 0 1 │ ├─────────────────┼───────────────────────────────┤ │ (X XOR C) AND M │ 0 0 0 1 0 1 0 0 │ └─────────────────┴───────────────────────────────┘ Вопрос к читателю: Какой вид будет иметь таблица-"подсказка" при отсут- ствии параметра C ? П р и м е р 3. 10 WAIT &HAA,64,255 ───────────── (&B∗1∗∗∗∗∗∗ XOR &B11111111) AND &B01000000 = &B00000000 = 0 ! Эта программа закончит свою работу, если загорится индикатор "CAPS". П р и м е р 4. ───────────── α) WAIT &H90,2,255 'Ожидается в к л ю ч е н и е принтера β) WAIT &H90,2,0 'Ожидается о т к л ю ч е н и е принтера X.11. Д о п о л н е н и е Р а б о т а с п о р т о м в в о д а - в ы в о д а с а д р е с о м &h0C Предварительно кратко опишем структуру данного порта. Старший ┌───┬───┬───┬───┬───┬───┬───┬───┐ Младший бит │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ ∗ │ бит └─▲─┴─▲─┴─▲─┴─▲─┴─▲─┴─▲─┴─▲─┴─▲─┘ │ │ │ │ │ │ │ └── Общий бит готовности сети │ │ Не используются │ (0: сеть готова) │ │ └────── Бит регистрации данных │ │ (0: данные поступили) │ └── Бит направления поступления информации (1: от учителя) └────── Бит направления поступления информации (1: от ученика) А теперь два примера его использования. В н и м а н и е ! Слабонервых просим не смотреть: примеры написаны на Макроассемблере M80! П р и м е р 1. П о с ы л к а байта по сети ────────────── OUT_BYTE:: ; На входе в регистре A - д а н н о е DI ; PUSH DE ; LD D,A ; LD A,005H ; OUT 9,5 - это запись байта в сетевое ОЗУ OUT (009H),A ; OUT_B: IN A,(0CH) ; AND 041H ; CP 040H ; JR NZ,OUT_B ; Если сеть не готова, то ждем LD A,D ; OUT (00EH),A ; POP DE ; EI ; RET ; П р и м е р 2. П р и е м байта из сети ────────────── IN_BYTE:: ; На выходе в регистре A - д а н н о е DI ; LD A,003H ; OUT 9,3 - считывание байта из сетевого ОЗУ OUT (009H),A ; IN_B: IN A,(00CH) ; AND 083H ; CP 080H ; JR NZ,IN_B ; Если сеть не готова, то ждем IN A,(00EH) ; EI ; RET ; К а р т а а д р е с о в п о р т о в в в о д а - в ы в о д а д л я к о м п ь ю т е р о в MSX-1 [30] FF·┌──────────────────────────────┐ B4·┌──────────────────────────────┐ │ │ │ Внешняя память │ F8·├──────────────────────────────┤ B0·├──────────────────────────────┤ │ Порты управления Audio/Video │ │ PPI (8255) │ F7·├──────────────────────────────┤ A8·├──────────────────────────────┤ │ │ │ PSG (AY-3-8910) │ F0·├──────────────────────────────┤ A0·├──────────────────────────────┤ │ │ │ VDP (9918A) │ E0·├──────────────────────────────┤ 98·├──────────────────────────────┤ │ ROM для китайских иероглифов │ │ Принтер │ D8·├──────────────────────────────┤ 90·├──────────────────────────────┤ │ Контроллер Floppy Disk │ │ │ D0·├──────────────────────────────┤ 88·├──────────────────────────────┤ │ │ │ RS-232C │ C0·├──────────────────────────────┤ 80·├──────────────────────────────┤ │ Световое перо │ │ Зарезервированы │ B8·├──────────────────────────────┤ 40·├──────────────────────────────┤ │ │ │ Не определены │ B5·├──────────────────────────────┤ 00·└──────────────────────────────┘ │ Календарь. Часы │ B4·└──────────────────────────────┘