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. ───────────── Работу этих двух программ Вы должны проверить на ученическом дисплее. α) Ok β) Ok 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