Глава 3
ЕЩЕ О БЕЙСИКЕ
Программист,
не знакомый с представлением чисел бейсик - интерпретатором компьютера ZX
Spectrum, немало удивится, столкнувшись со следующей программой:
10 CLEAR 100
20 PAPER 27: INK 8E-12: BORDER 65536: CLS
30 PRINT AT -10000,22E22;"PLEASE WAIT"
40 LOAD ""CODE 0,0
50 RANDOMIZE USR 0
Такой
программист будет настоятельно утверждать, что этот бред написан не иначе, как
пациентом учреждения на Пряжке, до тех пор, пока не запустит программу и не
увидит результаты ее работы. Что произойдет с программистом после того, как в
середине экрана появится надпись PLEASE WAIT, затем загрузится и запустится
игра, предсказать трудно. Вероятнее всего, он побежит в ПНД с предполагаемым
диагнозом «маниакально-депрессивный психоз на почве программирования».
Для
того чтобы с Вами этого не произошло, раскрою Вам одну тайну. Все дело в том,
что при обработке чисел бейсик-интерпретатор использует два их представления.
Первое представление — это приятная для глаз символьная форма, в которой число
16389, например, будет записано пятью байтами согласно таблице ASCII. Однако
такая форма относительно долго обрабатывается бейсик-интерпретатором, посему
для своих нужд он использует другое представление числа — двоичное. Двоичная
форма числа, предваряемая префиксом — кодом 14 (ОЕh), хранится в памяти
непосредственно после символьной формы. Число 16389, например, выглядит в
памяти следующим образом:
|
символьная форма
|
префикс
|
двоичная форма
|
|
|
1
|
6
|
3
|
8
|
9
|
|
...
|
49
|
54
|
51
|
56
|
57
|
14
|
0
|
0
|
5
|
64
|
0
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Отсюда
видно, что двоичная форма числа занимает 5 байт плюс байт префикса. При записи
целых констант в промежутке от 0 до 65535 «значащими» являются 3-й и 4-й байты
после префикса. В этом случае двоичную форму числа можно перевести в десятичную
с помощью оператора
PRINT PEEK (n+3)+256*РЕЕК (n+4)
- где
n — адрес, по которому хранится байт префикса. Двоичная и символьная
формы числа существуют параллельно.
При
выполнении инструкций бейсик-интерпретатор руководствуется только двоичной
формой, символьная форма его не интересует, она сохраняется ради
удобочитаемости программ. Поэтому «порча» символьного представления чисел
приводит в ужасный вид листинг программы, но не влияет на ее работоспособность.
Двоичная форма
числа добавляется в момент проверки
синтаксиса строки, то есть по окончании набора строки и нажатии клавиши Enter.
При вызове на редактирование строки двоичная форма удаляется, а после ввода
исправленной строки восстанавливается в соответствии с новой редакцией. Из
сказанного должно быть понятно, что загадочная программа, приведенная в начале
этой главы, не будет работать, если ее ввести с клавиатуры обычным способом, не
используя каких-либо специальных приемов (быть может, Вы уже попробовали ее
набрать и запустить и решили, что не кто иной, как я, и являюсь пациентом вышеуказанной
клиники).
Для
того, чтобы привести программу к такому извращенному виду, при этом оставив ее
работоспособной, необходимы внешние средства, например, монитор-отладчик MONS4
или подобные. Однако мы пока ведем разговор об адаптации программ на диск, а не
о приведении их в состояние алкогольного опьянения, поэтому задачи у нас стоят
обратные.
Можно
было бы заняться пространным описанием представления чисел в двоичной форме, но
я предпочту дать практические советы по вычислению того, что скрыто под маской
символьной формы.
Наиболее простым и эффективным способом определения
истинных значений чисел, по моему опыту, является подстановка оператора PRINT
вместо операторов программы. В этом случае все числа, используемые программой,
будут последовательно выведены на экран в нормальном виде.
Если
приведенную в начале главы программу изменить следующим образом (обратите
внимание, что не использующие чисел операторы и другие «лишние» символы
заменены пробелами, обозначенными в данном случае знаком подчеркивания):
10 PRINT 100
20 PRINT 27: PRINT 8E-12: PRINT 65536:_
30 PRINT _-10000,22E22;________________
40 PRINT ___0,0
50 PRINT _0
— то
в результате ее выполнения на экране появится:
24999
0
4
0
10 10
25000 40000
25000
0
ОК, 50:1
Подставив полученные значения в программу, получим привычный и понятный текст:
10 CLEAR 24999
20 PAPER 0: INK 4: BORDER 0: CLS
30 PRINT AT 10,10;"PLEASE WAIT"
40
LOAD ""CODE 25000,40000
50 RANDOMIZE USR 25000
Если
Вы уже потянулись к клавише Edit, чтобы побыстрее расставить операторы PRINT,
советую Вам прочесть все с начала. Как уже говорилось, при вызове на
редактирование двоичная форма числа безвозвратно теряется, остается только
символьная, и если Вы исправите INK 8E—12 на PRINT 8E—12 с
помощью бейсик-редактора, то и получите не что иное, как 8Е-12. Чтобы
заменить операторы программы на PRINT, не «сломав» двоичной формы чисел,
нужны другие способы. Можно воспользоваться все тем же отладчиком MONS4; в
крайнем случае сойдет и оператор РОКЕ. В этой брошюре я ставлю своей
целью обучать работе с отладчиком, поэтому расскажу лучше, как победить
программу без подручных средств.
Оператор
РОКЕ очень хорош, но только тогда, когда знаешь, какие параметры должны
следовать после него. А для этого нужно заглянуть в память компьютера. Можно,
например, воспользоваться строчкой, которая выводит на экран адрес и содержимое
ячеек памяти и символьное представление этого содержимого:
FOR
n=23755 TO 4E10: PRINT n,PEEK n;ТАВ 22;
CHR$ (PEEK n*(PEEK n=32)): NEXT n
Выполнив эту строку, на экране получим:
23755 0 ? номер строки
23756 10 ?
23757 11 ? длина строки
23758 0 ?
23759 253 CLEAR ключевое слово
23760 49 1
23761 48 0 символьная форма параметра
23762 48 О
23763 14 ? префикс
23764 0 ?
23765 0 ?
23766 167 COS двоичная форма параметра
23767 97
23768 0 ?
23769 13 ? возврат каретки
23770 0 ? номер строки
23771 20 ?
23772 38 &
23773 0 ?
23774 218 PAPER
23775 50 2
23776 55 7
scroll?
Теперь
нужно внимательно изучить содержимое экрана. Как Вы уже знаете, первые два
байта — это номер строки, следующие два — длина строки, далее ключевое слово CLEAR,
которое, собственно, нам и нужно. Берем листок бумаги и карандаш и записываем
адрес, по которому находится CLEAR (23759), и продолжаем сей высокоинтеллектуальный
труд. После CLEAR следует символьная форма числа, затем двоичная, далее
— символ «возврат каретки» (конец строки), номер и длина следующей строки и
оператор PAPER. Опять берем карандаш и записываем адрес оператора PAPER.
Просмотрев
программу до конца и записав все адреса, которые необходимо заменить, можно
приступать к изменениям:
РОКЕ 23759,245: REM Заменяем CLEAR
РОКЕ 23774,245: REM Заменяем PAPER
РОКЕ 23884,245: REM Заменяем INK
РОКЕ 23997,245: REM Заменяем BORDER
РОКЕ 24110,32: REM Заменяем CLS
РОКЕ 24117,32: REM Заменяем AT
и т. д. Число 245
— это код ключевого слова PRINT, a 32 — код пробела. В данном примере
пробелами заменяются следующие операторы и символы:
CLS
AT
"PLEASE
WAIT"
""CODE
USR
Когда
будут сделаны все необходимые изменения, просмотрите листинг программы, дабы
убедиться в том, что исправлено все, что нужно. Затем программу можно запустить
и изучать полученные результаты.
Иногда
эту операцию можно несколько упростить. Если в двоичном представлении чисел
используются целые константы от О до 65535, то определить их истинные значения
можно непосредственно при просмотре памяти. Для этого желательно добавить к
карандашу и бумаге калькулятор.
Найдите
в памяти символьную форму числа, которое нужно «разоблачить»; за ней следует
префикс и двоичная форма. Если число целое в промежутке от 0 до 65535, то после
префикса будут следовать два нуля, затем два числа в интервале от 0 до 255 и
еще один ноль. Если это не так, то определенно можно сказать, что число либо не
целое, либо выходит за пределы указанного диапазона. В этом случае проще
прибегнуть к предыдущему способу. Если число «подходит», то умножьте 4-й после
префикса байт на 256 и прибавьте 3-й (см. стр. 14). В рассматриваемом примере,
чтобы получить параметр оператора CLEAR, нужно вычислить:
PRINT PEEK (23763+3)+256*РЕЕК (23763+4)
или
PRINT 167+256*97
Полученный
результат (24999) и есть то, что от нас пытались утаить.
Раскроем
секрет еще одного распространенного фокуса, основанного на знании формата
записи чисел бейсик-интерпретатором (хотя, возможно, отгадку Вы уже знаете).
Вам,
наверняка, встречались подобные строки:
10 CLEAR VAL "24999": INK VAL "7": PAPER BIN:
BORDER BIN
Таким
образом программисты экономят оперативную память. Как ни парадоксально, но
запись VAL "24999" занимает меньше места, чем просто 24999.
Так как в первом случае число 24999 заключено в кавычки, то оно является
символьной строкой, а не числом и не имеет после себя шести байт двоичного
представления. При выполнении функции VAL эта строка переводится в
число. Таким образом экономится три байта. Что же касается BIN, то выигрыш
еще более очевиден. Если вместо BIN подставить 0, то памяти будет занято
на шесть байт больше, а результат — тот же.
Этот
способ экономии памяти несколько замедляет выполнение программы, но его можно и
нужно использовать в загрузчиках, так как именно они, как правило, наиболее
критичны к размеру и не критичны к скорости выполнения.