4. MEMORY 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 (see also page 19 and 23). 'RESERVE -n%' is useful if you are about to create a protected memory- block with MALLOC (see paragraph 'MALLOC', page 4-6) 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', page 4-3) 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 (page 4-6) 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 (page 4-1) 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.