Оператор DATA позволяет указывать не только фиксированные числа, но и именованные константы. Это гораздо удобнее, когда вы используете некоторые данные в качестве типовых значений.
Иногда в операторе DATA бывает удобно использовать не только фиксированные данные, но и каким-то образом сделать так, чтобы при считывании каждый раз считывалось актуальное значение какой-либо переменной.
На такой случай компилятор позволяет в операторе DATA поставить указатель на переменную.
Саму переменную затем можно побайтно вытащить по указателю при помощи inp().
Что вообще замечательно, так это то, что компилятор позволяет вставить в блок данных даже указатель на пользовательскую процедуру!
Это бывает очень полезно, например, при создании системы меню, где, помимо данных, содержится указатель на функцию, которая их должна обработать.
Для реализации такой возможности есть два способа: первый работает с использованием ключевого слова adr, второй -- с применением указателя на переменную (слово), в которую программа заранее (на этапе инициализации, скорее всего) должна поместить указатель на адрес требуемой процедуры. При этом второй способ хоть и несколько более громоздок, зато позволяет назначать процедуру-обработчик динамически.
Чтобы лучше усвоить материал, запустите на пошаговое выполнение в симуляторе пример реализации указанных возможностей. При этом наблюдайте содержимое регистров R8,9, которые BASCOM использует как указатель чтения операторов DATA, а также регистров R30,31, задающих адрес перехода по команде ! icall.
Дополнительно посмотрите справку по встроенным функциям adr, adr2 - там в качестве примера дана компактная реализация многоуровневого меню.
$regfile = "m328pdef.dat"
$crystal = 16000000
$hwstack = 48
$swstack = 48
$framesize = 64
$sim
' **** размещение и чтение из оператора DATA констант, переменных и адресов процедур (меток) ***
dim mystring as string * 4
dim s as string * 4
dim w as word
dim w2 as word
dim b as byte
dim b2 as byte
const myconst = 1
' --- чтение указателя на строку ---
mystring = "ABCD"
restore loc_1 ' указатель на начало таблицы данных
read b
read b
read w ' в w прочитаем адрес строки mystring
print hex(w) ' напечатать
s = "" ' }
do ' }
b = inp(w) ' }
s = s + chr(b) ' }
incr w ' }
loop until b = 0 ' } прочитать строку в s по адресу w
print s
' --- чтение адреса процедуры и ее выполнение ---
read dataptr ' загрузить в указатель DATA R8,9 адрес процедуры foo
!movw r30,r8 ' R8,9 -> R30,31 (адрес перехода)
!icall ' вызвать процедуру foo по адресу в R30,31
print "done"
restore loc_2 ' если после READ были операции, то указатель чтения R8, 9 возможно испорчен. обновить.
read dataptr ' загрузить в указатель DATA R8,9 адрес процедуры foo2
!movw r30,r8 ' R8,9 -> R30,31 (адрес перехода)
!icall ' вызвать процедуру foo2 по адресу в R30,31
print "done 2"
' ---- альтернативный способ --------------
w = loadlabel(foo3) ' получить адрес процедуры foo3 в переменную w
shift w , right ' чтобы получить адрес, надо поделить пополам
print "proc foo3 ptr = " ; hex(w)
restore loc_3 ' переместить указатель DATA в место, где хранится указатель на w
read w2 ' прочитать его в w2
b = inp(w2) ' и получить по указателю значение w
incr w2
b2 = inp(w2)
print "proc from data = " ; hex(b2) ; hex(b)
!LDS R30,{b} ' }
!LDS R31,{b2} ' } b,b2 -> R30,31 (адрес перехода)
!icall ' вызвать процедуру foo3 по адресу в R30,31
print "done 3"
' --- цепочечные данные --------------
restore loc_4
read b ' }
read b ' } прочитать пару dummy-значений
read w ' прочитать адрес следующей цепочки
print hex(w)
!lds R8, {w} ' }
!LDS R9, {w + 1} ' } установить указатель DATA на следующую метку
while w <> 0
read b ' }
read b ' } прочитать пару dummy-значений
read w ' прочитать адрес следующей цепочки
print hex(w)
wend
end
' --- процедуры ------------------------------------
foo:
print "foo"
return
foo2:
print "foo 2"
return
foo3:
print "foo 3"
return
' --- данные -----------------------------------
loc_1:
data myconst , 1 , varptr(mystring)
adr foo ' ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт)!!!
' в противном случае нужно поставить метку и сделать restore метка
loc_2:
adr foo2 ' ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт)!!!
' в противном случае нужно поставить метку и сделать restore метка
loc_3:
data varptr(w)
loc_4:
data 1 , 2 ' какие-то данные
adr2 loc_5 ' адрес следующих данных или 0, если конец цепочки
data 1 , 2 , 3 , 4 , 5 , 6 ' какие-то совсем другие данные
loc_5: ' следующая цепочка:
data 7 , 8 , 0% ' какие-то данные и 0 (слово) = конец цепочки
UPD: а еще в операторе DATA можно вычислять простые выражения. Когда это может пригодиться? Ну, например, какие-то константы у вас описаны как битовые поля, и вы можете использовать их как составные. Пример: data DATATYPE_DYNAMIC or DATATYPE_STRING задает битовые флаги признаков последующих данных.
UPD2: перед чтением командой read всегда инициализируйте указатель (командой restore метка или прямой загрузкой R8,9)! В начале программы он смотрит вообще неизвестно куда; и в процессе выполнения некоторых команд тоже может испортиться.
UPD3: не забывайте, что ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт). При этом следует учитывать: если среди данных имеются строки, то строка занимает число_символов + 1 байт.
Таким образом, код
data1:
data 1 , 2 , "ABCD" , 3
adr foo
будет работать, поскольку указатель на процедуру имеет четное смещение (перед ним 8 байт: 3 байта представлены числами 1, 2 и 3, и 5 байт занимает строка "ABCD".
В то же время код
data1:
data 1 , 2 , "ABC" , 3
adr foo
не сработает, поскольку указатель на процедуру расположен по нечетному адресу.
Что делать, если при какой-либо строке указатель не выровнен пословно? Добавьте к строке пробел, а после чтения обработайте ее при помощи trim.
Отредактировано Cirno_9 (2019-08-19 15:22:26)
