"I am not bald. I am just taller than my hair."
Clive Andersen
YOUR SECOND GFA BASIC 3.XX MANUAL
- or -
HOW I LEARNED TO STOP WORRYING AND LOVE GFA-BASIC
PART 5
CHAPTER FOUR - MEMORY
by Han Kempen
RAM
An overview of the memory of my 1040 ST (start at the bottom):
address
LPEEK(&H42E)-1 &H FFFFF top of memory of 1040 ST (1024 K)
&H FFD00 767 unused (?) bytes above screen
XBIOS(2) &H F8000 screen memory (32000 bytes)
HIMEM &H F4000 some unused bytes below screen
&H ..... free memory, length FRE() bytes
&H 388EA program + variables (length varies)
&H 10C2E GFA-Basic 3.07 interpreter
BASEPAGE &H 10B2E Basepage GFA-Basic (256 bytes)
&H A100 start of available RAM
&H 6100 global AES-variables
&H 29B4 global BIOS- and GEMDOS-variables
L~A &H 293A Line A variables
&H 93A local BIOS-variables + BIOS-stack
&H 400 BIOS system-variables
&H 0 exception vectors
The BIOS system-variables (&H400 - &H4FF) are "cast in concrete"
by Atari. Other (undocumented) variables in RAM should be avoided
like the plague. Line A variables are reasonably safe for the
time being, because Atari promised not to change these. Read more
about this promise in the paragraph 'Line-A' in chapter 20.
INT{}
You can use either 'INT{adr%}' or 'WORD{adr%}'. As you probably
know, 'w=WORD{adr%}' is faster than 'w=DPEEK(adr%)', but you
can't use WORD{} and the other related commands in supervisor
mode. This means you can't access memory below address &H800. Of
course you can PEEK/DPEEK/LPEEK everywhere (DPEEK and LPEEK on
even addresses only), and you can use SPOKE/SDPOKE/SLPOKE to
write in supervisor mode.
It's impossible to use INT/WORD/DPEEK on an odd address, but
that doesn't mean you can't read a word on an odd address (e.g.
in a buffer):
word&=ADD(BYTE{SUCC(adr%)},MUL(256,BYTE{adr%}))
RESERVE
The command RESERVE can be used in three different ways:
RESERVE n% : reserve n% bytes for program, release RAM up
to HIMEM
RESERVE -n% : release last n% bytes of RAM up to HIMEM
RESERVE : restore to original
You must use a multiple of 256 with RESERVE. After 'RESERVE -
400', only 256 bytes are released to GEMDOS. In this case you
would have to use 'RESERVE -512', although you need only 400
bytes. After 'RESERVE -255' (or any other number smaller than
256) no bytes are released to GEMDOS! After RESERVE the GFA
editor-screen will start on a new address. Because a screen-
address has to be a multiple of 256 (see chapter 9, paragraph
'Setscreen'), GFA uses the same multiple for RESERVE. Although
there is no editor-screen in a compiled program, you'll have to
use a multiple of 256 there as well.
'RESERVE n%' can be used if you want to reserve a limited amount
of memory for variables and arrays. This is especially important
for accessories. Replace the RESERVE-line by '$m....' before
compiling the program.
'RESERVE -n%' is useful if you are about to create a protected
memory-block with MALLOC (see paragraph 'MALLOC') or are going to
run another program through EXEC (see paragraph 'EXEC', chapter
19).
Use 'RESERVE -n%' only once in your program. If you RESERVE
memory a second time with 'RESERVE -m%', GFA releases m% bytes,
not n%+m%.
The command 'RESERVE' (restore) should allocate all memory up to
the original HIMEM-address back to the GFA-program, but it does
not always function properly. Especially after 'EXEC 3' it's
often impossible to restore the memory. I suspect this has
something to do with the use of the malloc-function by the
operating system. In case of difficulties you could try the
following:
RESERVE -n% ! reserve as much as needed (multiple of
256)
base%=EXEC(3,...) ! load, but don't start yet
(...)
~MFREE(HIMEM) ! memory above GFA-Basic
~MFREE(base%) ! memory from Basepage of loaded program
RESERVE ! hope it works now
Don't be surprised by hang-ups or bombs after this operation.
Don't call me, I'll call you...
Storing data in RAM
There are several ways to reserve a part of memory for special
purposes such as music, pictures or even other programs. The four
most used methods for storing a data-block in RAM use one of the
following:
- string-variable
- byte-array
- INLINE-line
- MALLOCated memory-block
You could use a string-variable to store a complete screen or a
GET-picture:
SGET screen$ ! save complete screen
GET x1,y1,x2,y2,pic$ ! save rectangular picture from
screen
You could load an assembler-routine into a string as follows:
OPEN "I",#1,file$
LET bytes%=LOF(#1) ! how much space is needed?
CLOSE #1
routine$=STRING$(bytes%,0) ! create space for assembler
routine
~FRE() ! force a garbage collection
adr%=V:routine$ ! where shall we put the
routine?
BLOAD file$,adr% ! load the routine from disk
Don't use strings if garbage collection is a serious risk. If a
large array is declared in your program, the interpreter
sometimes moves the strings in memory to create space for the new
array. This is necessary because an old string is not erased from
memory if you assign a new string to an existing string-variable.
During garbage collection all unused strings are deleted (except
strings that are exactly 32767 bytes long: a GFA-bug) and the
active strings are rearranged. This means that the address of a
string (accessed through 'VARPTR' or 'V:') is not fixed. That's
not important for a (S)GET-picture, because 'PUT x,y,pic$' and
'SPUT screen$' still work all right. But if you assigned a
string-address to a variable adr% and swap screens with
'~XBIOS(5,L:adr%,-1,-1)', or call a routine with '~C:adr%(...)',
garbage collection will be fatal. One solution is not to declare
a variable for the address, but to use 'VARPTR' or 'V:' so you'll
always find the correct address.
If you have nothing better to do, you could watch how GFA
collects garbage (don't forget to wash your hands afterwards):
s1$=STRING$(1000,32) ! create three strings
s2$=STRING$(1000,32)
s3$=STRING$(1000,32)
adr.1%=V:s1$ ! where are the strings?
adr.2%=V:s2$
adr.3%=V:s3$
PRINT adr.1%'adr.2%'adr.3%
s2$="" ! delete the second string
adr.1%=V:s1$ ! where are the strings now?
adr.2%=V:s2$
adr.3%=V:s3$
PRINT adr.1%'adr.2%'adr.3% ! nothing has changed
~FRE(0) ! force garbage collection
adr.1%=V:s1$ ! where are the strings now?
adr.2%=V:s2$
adr.3%=V:s3$
PRINT adr.1%'adr.2%'adr.3% ! watch the third string
~INP(2) ! wait for keypress
After deletion of the second string, nothing happened with the
third string. But after a (forced) garbage collection, the third
string was moved to the address that was previously occupied by
the (now deleted) second string. The first string was not
affected in this case, because there was no garbage before the
string (actually above the string if you examine how the strings
are stored in RAM). By the freeway, if you use 'FRE()' you get
the amount of free memory without first forcing a garbage
collection. With 'FRE(0)' garbage is collected, unless you left
an old string of exactly 32767 bytes lying around. That will
cause a complete hang-up in the interpreter, while a compiled
program simply ignores the 32767 wasted bytes. Now that's what I
call a real bug.
A slightly safer method for storing a data-block in memory uses
a byte-array instead of a string:
OPEN "I",#1,file$
LET bytes%=LOF(#1) ! how much space is needed?
CLOSE #1
DIM assembler|(bytes%-1) ! create space for assembler
routine
adr%=V:assembler|(0) ! where shall we put the
routine?
BLOAD file$,adr% ! load the routine
You can use the variable adr% safely, because a garbage
collection has no influence on arrays. However, there is another
problem. After ERASEing an array, all arrays that have been
DIMensioned after the deleted array are moved in memory. So the
array-method is not reliable either, unless you are certain that
ERASE will not be used after you have determined the address of
the byte-array. Another solution would be to declare the byte-
array early in your program, so it will not be moved after ERASE.
Here is a demonstration of the problem (no need to wash your
hands this time):
DIM array.1|(1000),array.2|(1000),array.3|(1000) ! create
arrays
adr.1%=V:array.1|(0) ! where are the arrays?
adr.2%=V:array.2|(0)
adr.3%=V:array.3|(0)
PRINT adr.1%'adr.2%'adr.3%
ERASE array.2|() ! delete the second array
adr.1%=V:array.1|(0) ! where are the arrays now?
adr.3%=V:array.3|(0)
PRINT adr.1%'" - "'adr.3% ! watch the third array
~INP(2) ! wait for keypress
First, 3000 bytes of free memory are used for three byte-arrays.
After erasing the second array, you'll discover that the third
array has been moved down. The third array now occupies RAM that
previously contained the (now deleted) second array. The address
of the third array has changed after ERASE and in this case is
now equal to the previous address of the second array. The first
array is not affected by ERASE because it was declared before the
second array.
How to store data in an INLINE-line or a MALLOC-block is
described in the following two paragraphs.
INLINE
Use D (=DUMP) to dump an INLINE-file in Hex-code to the printer.
Don't use 'Save,A', because you will lose the INLINE-code. You
can't use INLINE in LST-files. If you would like to use INLINE in
combination with a LST-file you could proceed as follows. First,
create an INLINE-folder in the main directory and SAVE the
INLINE-code as a file (extension .INL) in this folder. Merge the
LST-file in your program and load the INLINE-code in the INLINE-
line. The program should be saved with 'Save' as a GFA-file.
The main advantage of INLINE is that you don't have to load the
data from disk. Of course you could ignore that and do something
like this:
' create an INLINE-line of the proper length (1000 bytes)
INLINE adr%,1000
BLOAD file$,adr% ! load the data from disk
In the paragraph 'GET and PUT' (chapter 20) you'll see how you
can store a (GET-)string in an INLINE-line. In the paragraph
'Degas-Pictures' (chapter 20) you'll learn how to keep a Degas-
picture instantly available in an INLINE-line.
The INLINE-method is completely safe, because garbage collection
or ERASEing arrays (see previous paragraph) has no influence on
the data in the INLINE-line.
The only limitation with INLINE is the maximum length of 32746
bytes. If you need a contiguous block of more than 32746 bytes,
you have to use MALLOC.
A disadvantage of INLINE is that the programmer must know in
advance exactly how much memory is needed. A byte-array is
created while the program is running, so the user can determine
how much memory is needed.
If you change the length of an existing INLINE-line, the editor
sometimes erases a few lines from your program. It's safer to
completely delete the old INLINE-line and then enter the new
INLINE-line.
If you are going to do some heavy editing you should place all
INLINE-lines at the start of your program. If you edit a line
before an INLINE-line, the INLINE-code is lost and you'll have to
load it again from disk. Placing the INLINE-lines at the start of
your program (as in the standard program-structure STANDARD.GFA,
page 19) will prevent that.
MALLOC
This is how you could use MALLOC to create a "protected" memory-
block:
RESERVE -bytes% ! shrink GFA-program
adr%=MALLOC(bytes%) ! create memory-block
Atari recommends you leave at least 8K to GEM.
Your data in a MALLOC-block are completely safe, because garbage
collection or ERASEing arrays (see paragraph 'Storing data in
RAM') has no influence on the data in the MALLOC-block.
Do not use MALLOC to allocate a lot of small memory-blocks, as
GEMDOS will get confused. This problem is related to the "40-
folder limit", because MALLOC uses the same buffer that is used
to store information about a folder (see paragraph 'FILESELECT'
in chapter 11). Allocate one large area and split it up in as
many parts as you need. Also consider the use of INLINE, strings
or byte-arrays instead of using MALLOC (read the previous
paragraphs 'Storing data in RAM' and 'INLINE' in this chapter).
Be careful with MALLOC in an accessory, as the allocated memory
may be lost if the user changes the resolution while your program
is running. I know, the user is not supposed to do that, but you
never can tell...
MALLOC(-1) returns the size of the largest available memory-
block. Expect problems if another program has allocated a couple
of separate memory-blocks to GEMDOS.
BMOVE
You can use BMOVE with numerical arrays in random-access files as
follows:
DIM numbers&(99) ! that's 100*2 = 200
bytes
' Save
OPEN "R",#1,file$,225
FIELD #1,10 AS f1$,15 AS f2$,200 AS f3$
LSET f1$=something$
LSET f2$=something.else$
BMOVE V:numbers&(0),V:f3$,200 ! put number-array in
field
PUT #1,record ! write record to disk
CLOSE #1
' Load
OPEN "R",#1,file$,225
FIELD #1,10 AS f1$,15 AS f2$,200 AS f3$
GET #1,record ! load record from disk
BMOVE V:f3$,V:numbers&(0),200 ! put data in number
array
CLOSE #1
This method can only be used if you know excactly how many
elements the numerical array will contain, so you can calculate
the number of bytes you'll need in the field. This means the
DIMension of the array can't be changed. Apart from this
restriction, this method is quite useful because of the gain in
speed compared to using a sequential file for storing the
elements in the numerical array.
Procedures (CHAPTER.04)
Memory_block MEMBLOCK
Create a protected memory-block:
@memory_block(100*1024,adr%) ! block of 100 K at adr%
(...) ! use memory-block at address
adr%
@memory_block_free(adr%) ! release memory-block
(important!)
Functions (CHAPTER.04)
Word WORD
Read a word on an odd address in RAM (? &H800):
w&=@word(adr%)
On an even address WORD{} is much faster, but you can't use
WORD{} on an odd address.
Disclaimer
The text of the articles is identical to the originals like they appeared
in old ST NEWS issues. Please take into consideration that the author(s)
was (were) a lot younger and less responsible back then. So bad jokes,
bad English, youthful arrogance, insults, bravura, over-crediting and
tastelessness should be taken with at least a grain of salt. Any contact
and/or payment information, as well as deadlines/release dates of any
kind should be regarded as outdated. Due to the fact that these pages are
not actually contained in an Atari executable here, references to scroll
texts, featured demo screens and hidden articles may also be irrelevant.