Skip to main content

 "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.