amiga-e/amigae33a/E_v3.3a/Docs/BeginnersGuide/Memory.guide

558 lines
24 KiB
Plaintext

@database beginner.guide
@Master beginner
@Width 75
This is the AmigaGuide® file beginner.guide, produced by Makeinfo-1.55 from
the input file beginner.
@NODE "main" "Memory Allocation"
@Next "FloatingPoint.guide/main"
@Prev "Exceptions.guide/main"
@Toc "Contents.guide/main"
Memory Allocation
*****************
When a program is running memory is being used in various different
ways. In order to use any memory it must first be @{fg shine }allocated@{fg text }, which is
simply a way of marking memory as being `in use'. This is to prevent the
same piece of memory being used for different data storage (e.g., by
different programs), and so helps prevent corruption of the data stored
there. There are two general ways in which memory can be allocated:
dynamically and statically.
@{" Static Allocation " Link "Static Allocation" }
@{" Deallocation of Memory " Link "Deallocation of Memory" }
@{" Dynamic Allocation " Link "Dynamic Allocation" }
@{" NEW and END Operators " Link "NEW and END Operators" }
@ENDNODE
@NODE "Static Allocation" "Static Allocation"
@Next "Deallocation of Memory"
@Toc "main"
Static Allocation
=================
@{fg shine }Statically@{fg text } allocated memory is memory allocated by the program for
variables and static data like string constants, lists and typed lists
(see @{"Static data" Link "Types.guide/Static data" }). Every variable in a program requires some memory in
which to store its value. Variables declared to be of type @{b }ARRAY@{ub }, @{b }LIST@{ub },
@{b }STRING@{ub } or any object require two lots of memory: one to hold the value of
the pointer and one to hold the large amount of data (e.g., the elements
in an @{b }ARRAY@{ub }). In fact, such declarations are merely @{b }PTR TO @{ub }@{fg shine }type@{fg text }@{b }@{ub }
declarations together with an initialisation of the pointer to the address
of some (statically) allocated memory to hold the data. The following
example shows very similar declarations, with the difference being that in
the second case (using @{b }PTR@{ub }) only memory to hold the pointer values is
allocated. The first case also allocates memory to hold the appropriate
size of array, object and E-string.
DEF a[20]:ARRAY, m:myobj, s[10]:STRING
DEF a:PTR TO CHAR, m:PTR TO myobj, s:PTR TO CHAR
The pointers in the second case are not initialised by the declaration
and, therefore, they are not valid pointers. This means that they should
not be dereferenced in any way, until they have been initialised to the
address of some allocated memory. This usually involves dynamic
allocation of memory (see @{"Dynamic Allocation" Link "Dynamic Allocation" }).
@ENDNODE
@NODE "Deallocation of Memory" "Deallocation of Memory"
@Next "Dynamic Allocation"
@Prev "Static Allocation"
@Toc "main"
Deallocation of Memory
======================
When memory is allocated it is, conceptually, marked as being `in use'.
This means that this piece of memory cannot be allocated again, so a
different piece will be allocated (if any is available) when the program
wants to allocate some more. In this way, variables are allocated
different pieces of memory, and so their values can be distinct. But
there is only a certain amount of memory available, and if it could not be
marked as `not in use' again it would soon run out (and the program would
come to a nasty end). This is what @{fg shine }deallocation@{fg text } does: it marks
previously allocated memory as being `not in use' and so makes it
available for allocation again. However, memory should be deallocated
only when it is actually no longer in use, and this is where things get a
bit complicated.
Memory is such a vital resource in every computer that it is important
to use as little of it as necessary and to deallocate it whenever possible.
This is why a programming language like E handles most of the memory
allocation for variables. The memory allocated for variables can be
automatically deallocated when it is no longer possible for the program to
use that variable. However, this automatic deallocation is not useful for
global variables, since they can be used from any procedure and so can be
deallocated only when the program terminates. A procedure's local
variables, on the other hand, are allocated when the procedure is called
but cannot be used after the procedure returns. They can, therefore, be
deallocated when the procedure returns.
Pointers, as always, can cause big problems. The following example
shows why you need to be careful when using pointers as the return value
of a procedure.
/* This is an example of what *NOT* to do */
PROC fullname(first, last)
DEF full[40]:STRING
StrCopy(full, first)
StrAdd(full, ' ')
StrAdd(full, last)
ENDPROC full
PROC main()
WriteF('Name is \\s\\n', fullname('Fred', 'Flintstone'))
ENDPROC
On first sight this seems fine, and, in fact, it may even work correctly
if you run it once or twice (but be careful: it could crash your machine).
The problem is that the procedure @{b }fullname@{ub } returns the value of the local
variable @{b }full@{ub }, which is a pointer to some statically allocated memory for
the E-string and this memory will be deallocated when the procedure
returns. This means that the return value of any call to @{b }fullname@{ub } is the
address of recently deallocated memory, so it is invalid to dereference it.
But the call to @{b }WriteF@{ub } does just that: it dereferences the result of
@{b }fullname@{ub } in order to print the E-string it points to. This is a very
common problem, because it is such an easy thing to do. The fact that it
may, on many occasions, appear to work makes it much harder to find, too.
The solution, in this case, is to use dynamic allocation (see
@{"Dynamic Allocation" Link "Dynamic Allocation" }).
If you're still a bit sceptical that this really is a problem, try the
above @{b }fullname@{ub } procedure definition with either of these replacement @{b }main@{ub }
procedures, but be aware, again, that each one has the potential to crash
your machine.
/* This might not print the correct string */
PROC main()
DEF f
f:=fullname('Fred', 'Flintstone')
WriteF('Name is \\s\\n', f)
ENDPROC
/* This will definitely print g instead of f */
PROC main()
DEF f, g
f:=fullname('Fred', 'Flintstone')
g:=fullname('Barney', 'Rubble')
WriteF('Name is \\s\\n', f)
ENDPROC
(The reason why things go wrong is outlined above, but the reasons why
each prints what it does is beyond the scope of this Guide.)
@ENDNODE
@NODE "Dynamic Allocation" "Dynamic Allocation"
@Next "NEW and END Operators"
@Prev "Deallocation of Memory"
@Toc "main"
Dynamic Allocation
==================
@{fg shine }Dynamically@{fg text } allocated memory is any memory that is not statically
allocated. To allocate memory dynamically you can use the @{b }List@{ub } and @{b }String@{ub }
functions, all flavours of @{b }New@{ub }, and the versatile @{b }NEW@{ub } operator. But
because the memory is dynamically allocated it must be explicitly
deallocated when no longer needed. In all the above cases, though, any
memory that is still allocated when the program terminates will be
deallocated automatically.
Another way to allocate memory dynamically is to use the Amiga system
functions based on @{b }AllocMem@{ub }. However, these functions require that the
memory allocated using them be deallocated (using functions like @{b }FreeMem@{ub })
before the program terminates, or else it will never be deallocated (not
until your machine is rebooted, anyway). It is safer, therefore, to try
to use the E functions for dynamic allocation whenever possible.
There are many reasons why you might want to use dynamic allocation,
and most of them involve initialisation of pointers. For example, the
declarations in the section about static allocation can be extended to
give initialisations for the pointers declared in the second @{b }DEF@{ub } line (see
@{"Static Allocation" Link "Static Allocation" }).
DEF a[20]:ARRAY, m:myobj, s[10]:STRING
DEF a:PTR TO CHAR, m:PTR TO myobj, s:PTR TO CHAR
a:=New(20)
m:=New(SIZEOF myobj)
s:=String(20)
These are initialisations to dynamically allocated memory, whereas the
first line of declarations initialise similar pointers to statically
allocated memory. If these sections of code were part of a procedure
then, since they would now be local variables, there would be one other,
significant difference: the dynamically allocated memory would not
automatically be deallocated when the procedure returns, whereas the
statically allocated memory would. This means that we can solve the
deallocation problem (see @{"Deallocation of Memory" Link "Deallocation of Memory" }).
/* This is the correct way of doing it */
PROC fullname(first, last)
DEF full
full:=String(40)
StrCopy(full, first)
StrAdd(full, ' ')
StrAdd(full, last)
ENDPROC full
PROC main()
DEF f, g
WriteF('Name is \\s\\n', fullname('Fred', 'Flintstone'))
f:=fullname('Fred', 'Flintstone')
g:=fullname('Barney', 'Rubble')
WriteF('Name is \\s\\n', f)
ENDPROC
The memory for the E-string pointed to by @{b }full@{ub } is now allocated
dynamically, using @{b }String@{ub }, and is not deallocated until the end of the
program. This means that it is quite valid to pass the value of @{b }full@{ub } as
the result of the procedure @{b }fullname@{ub }, and it is quite valid to dereference
the result by printing it using @{b }WriteF@{ub }. However, this has caused one last
problem: the memory is not deallocated until the end of the program, so is
potentially wasted since it could be used, for example, to hold the
results of subsequent calls. Of course, the memory can be deallocated
only when the data it stores is no longer required. The following
replacement @{b }main@{ub } procedure shows when you might want to deallocate the
E-string (using @{b }DisposeLink@{ub }).
PROC main()
DEF f, g
f:=fullname('Fred', 'Flintstone')
WriteF('Name is \\s, f points to $\\h\\n', f, f)
/* Try this with and without the next DisposeLink line */
DisposeLink(f)
g:=fullname('Barney', 'Rubble')
WriteF('Name is \\s, g points to $\\h\\n', g, g)
DisposeLink(g)
ENDPROC
If you run this with the @{b }DisposeLink(f)@{ub } line you'll probably find that
@{b }g@{ub } will be a pointer to the same memory as @{b }f@{ub }. This is because the call
to @{b }DisposeLink@{ub } has deallocated the memory pointed to by @{b }f@{ub }, so it can be
reused to store the E-string pointed to by @{b }g@{ub }. If you comment out (or
delete) the @{b }DisposeLink@{ub } line, then you will find that @{b }f@{ub } and @{b }g@{ub } always point
to different memory.
In some ways it is best to never do any deallocation, because of the
problems you can get into if you deallocate memory too early (i.e., before
you've finished with the data it contains). Of course, it is safe (but
temporarily wasteful) to do this with the E dynamic allocation functions,
but it is very wasteful (and wrong) to do this with the Amiga system
functions like @{b }AllocMem@{ub }.
Another benefit of using dynamic allocation is that the size of the
arrays, E-lists and E-strings that can be created can be the result of any
expression, so is not restricted to constant values. (Remember that the
size given on @{b }ARRAY@{ub }, @{b }LIST@{ub } and @{b }STRING@{ub } declarations must be a constant.)
This means that the @{b }fullname@{ub } procedure can be made more efficient and
allocate only the amount of memory it needs for the E-string it creates.
PROC fullname(first, last)
DEF full
/* The extra +1 is for the added space */
full:=String(StrLen(first)+StrLen(last)+1)
StrCopy(full, first)
StrAdd(full, ' ')
StrAdd(full, last)
ENDPROC full
However, it may be very complicated or inefficient to calculate the
correct size. In these cases, a quick, constant estimate might be better,
overall.
The various functions for allocating memory dynamically have
corresponding functions for deallocating that memory. The following table
shows some of the more common pairings.
Allocation Deallocation
------------------------------
New Dispose
NewR Dispose
List DisposeLink
String DisposeLink
NEW END
FastNew FastDispose
AllocMem FreeMem
AllocVec FreeVec
AllocDosObject FreeDosObject
@{b }NEW@{ub } and @{b }END@{ub } are versatile and powerful operators, discussed in the
following section. The functions beginning with @{b }Alloc-@{ub } are Amiga system
functions and are paired with similarly suffixed functions with a @{b }Free-@{ub }
prefix. See the `Rom Kernel Reference Manual' for more details.
@ENDNODE
@NODE "NEW and END Operators" "NEW and END Operators"
@Prev "Dynamic Allocation"
@Toc "main"
@{b }NEW@{ub } and @{b }END@{ub } Operators
=====================
To help deal with dynamic allocation and deallocation of memory there
are two, powerful operators, @{b }NEW@{ub } and @{b }END@{ub }. The @{b }NEW@{ub } operator is very
versatile, and similar in operation to the @{b }New@{ub } family of built-in
functions (see @{"System support functions" Link "BuiltIns.guide/System support functions" }). The @{b }END@{ub } operator is the
deallocating complement of @{b }NEW@{ub } (so it is similar to the @{b }Dispose@{ub } family of
built-in functions). The major difference between @{b }NEW@{ub } and the various
flavours of @{b }New@{ub } is that @{b }NEW@{ub } allocates memory based on the types of its
arguments.
@{" Object and simple typed allocation " Link "Object and simple typed allocation" }
@{" Array allocation " Link "Array allocation" }
@{" List and typed list allocation " Link "List and typed list allocation" }
@{" OOP object allocation " Link "OOP object allocation" }
@ENDNODE
@NODE "Object and simple typed allocation" "Object and simple typed allocation"
@Next "Array allocation"
@Toc "NEW and END Operators"
Object and simple typed allocation
----------------------------------
The following sections of code are roughly equivalent and serve to show
the function of @{b }NEW@{ub }, and how it is closely related to @{b }NewR@{ub }. (The @{fg shine }type@{fg text }
can be any object or simple type.)
DEF p:PTR TO @{fg shine }type@{fg text }
NEW p
DEF p:PTR TO @{fg shine }type@{fg text }
p:=NewR(SIZEOF @{fg shine }type@{fg text })
Notice that the use of @{b }NEW@{ub } is not like a function call, as there are no
parentheses around the parameter @{b }p@{ub }. This is because @{b }NEW@{ub } is an operator
rather than a function. It works differently from a function, since it
also needs to know the types of its arguments. This means that the
declaration of @{b }p@{ub } is very important, since it governs how much memory is
allocated by @{b }NEW@{ub }. The version using @{b }NewR@{ub } explicitly gives the amount of
memory to be allocated (using the @{b }SIZEOF@{ub } operator), so in this case the
declared type of @{b }p@{ub } is not so important for correct allocation.
The next example shows how @{b }NEW@{ub } can be used to initialise several
pointers at once. The second section of code is roughly equivalent, but
uses @{b }NewR@{ub }. (Remember that the default type of a variable is @{b }LONG@{ub }, which
is actually @{b }PTR TO CHAR@{ub }.)
DEF p:PTR TO LONG, q:PTR TO myobj, r
NEW p, q, r
DEF p:PTR TO LONG, q:PTR TO myobj, r
p:=NewR(SIZEOF LONG)
q:=NewR(SIZEOF myobj)
r:=NewR(SIZEOF CHAR)
These first two examples have shown the statement form of @{b }NEW@{ub }. There
is also an expression form, which has one parameter and returns the
address of the newly allocated memory as well as initialising the argument
pointer to this address.
DEF p:PTR TO myobj, q:PTR TO myobj
q:=NEW p
DEF p:PTR TO myobj, q:PTR TO myobj
q:=(p:=NewR(SIZEOF @{fg shine }type@{fg text }))
This may not seem desperately useful, but it's also the way that @{b }NEW@{ub } is
used to allocate copies of lists and typed lists (see
@{"List and typed list allocation" Link "List and typed list allocation" }).
To deallocate memory allocated using @{b }NEW@{ub } you use the @{b }END@{ub } statement with
the pointers that you want to deallocate. To work properly, @{b }END@{ub } requires
that the type of each pointer matches the type used when it was allocated
with @{b }NEW@{ub }. Failure to do this will result in an incorrect amount of memory
being deallocated, and this can cause many subtle problems in a program.
You must also be careful not to deallocate the same memory twice, and to
this end the pointers given to @{b }END@{ub } are re-initialised to @{b }NIL@{ub } after the
memory they point to is deallocated (it is quite safe to use @{b }END@{ub } with a
pointer which is @{b }NIL@{ub }). This does not catch all problems, however, since
more than one pointer can point to the same piece of memory, as shown in
the example below.
DEF p:PTR TO LONG, q:PTR TO LONG
q:=NEW p
p[]:=-24
q[]:=613
END p
/* p is now NIL, but q is now invalid but not NIL */
The first assignment initialises @{b }q@{ub } to be the same as @{b }p@{ub } (which is
initialised by @{b }NEW@{ub }). @{i }Both@{ui } the next two assignments change the value
pointed to by @{i }both@{ui } @{b }p@{ub } and @{b }q@{ub }. The memory allocated to store this value is
then deallocated, using @{b }END@{ub }, and this also sets @{b }p@{ub } to @{b }NIL@{ub }. However, the
address stored in @{b }q@{ub } is not altered, and still points to the memory that
has just been deallocated. This means that @{b }q@{ub } now has a plausible, but
invalid, pointer value. The only thing that can safely be done with @{b }q@{ub } is
re-initialise it. One of the @{i }worst@{ui } things that could be done is to use it
with @{b }END@{ub }, which would deallocate the same memory again, and potentially
crash your machine. So, in summary, don't deallocate the same pointer
value more than once, and keep track of which variables point to the same
memory as others.
Just as a use of @{b }NEW@{ub } has a simple (but rough) equivalent using @{b }NewR@{ub },
@{b }END@{ub } has an equivalent using @{b }Dispose@{ub }, as shown by the following
sections of code.
END p
IF p
Dispose(p)
p:=NIL
ENDIF
In fact, it's a tiny bit more complicated than that, since OOP objects are
allocated and deallocated using @{b }NEW@{ub } and @{b }END@{ub } (see @{"Object Oriented E" Link "OOE.guide/main" }).
@ENDNODE
@NODE "Array allocation" "Array allocation"
@Next "List and typed list allocation"
@Prev "Object and simple typed allocation"
@Toc "NEW and END Operators"
Array allocation
----------------
Arrays can also be allocated using @{b }NEW@{ub }, and this works in a very
similar way to that outlined in the previous section. The difference is
that the size of the array must also be supplied, in both the use of @{b }NEW@{ub }
and @{b }END@{ub }. Of course, the size supplied to @{b }END@{ub } must be the same as the size
supplied to the appropriate use of @{b }NEW@{ub }. All this extra effort also gains
you the ability to create an array of a size which is not a constant
(unlike variables of type @{b }ARRAY@{ub }). This means that the size supplied to
@{b }NEW@{ub } and @{b }END@{ub } can be the result of an arbitrary expression.
DEF a:PTR TO LONG, b:PTR TO myobj, s
NEW a[10] /* A dynamic array of LONG */
s:=my_random(20)
NEW b[s] /* A dynamic array of myobj */
/* ...some other code... */
END a[10], b[s]
The @{b }my_random@{ub } function stands for some arbitrary calculation, to show that
@{b }s@{ub } does not have to be a constant. This form of @{b }NEW@{ub } can also be used as an
expression, as before.
@ENDNODE
@NODE "List and typed list allocation" "List and typed list allocation"
@Next "OOP object allocation"
@Prev "Array allocation"
@Toc "NEW and END Operators"
List and typed list allocation
------------------------------
Lists and typed lists are usually static data, but @{b }NEW@{ub } can be used to
create dynamically allocated versions. This form of @{b }NEW@{ub } can be used only
as an expression, and it takes the list (or typed list) as its argument
and returns the address of the dynamically allocated copy of the list.
Deallocation of the memory allocated in this way is a bit more complicated
than before, but you can, of course, let it be deallocated automatically
at the end of the program.
The following example shows how simple it is to use @{b }NEW@{ub } to cure the
static data problem described previously (see @{"Static data" Link "Types.guide/Static data" }). The
difference from the original, incorrect program is very subtle.
PROC main()
DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG
FOR i:=0 TO 9
a[i]:=NEW [1, i, i*i]
/* a[i] is now dynamically allocated */
ENDFOR
FOR i:=0 TO 9
p:=a[i]
WriteF('a[\\d] is an array at address \\d\\n', i, p)
WriteF(' and the second element is \\d\\n', p[1])
ENDFOR
ENDPROC
The minor alteration is to prefix the list with @{b }NEW@{ub }, thereby making the
list dynamic. This means that each @{b }a[i]@{ub } is now a different list, rather
than the same, static list of the original version of the program.
Typed lists are allocated in a similar way, and the following example
also shows how to deallocate this memory. Basically, you need to know how
long the new array is (i.e., how many elements there are), since a typed
list is really just an initialised array. You can then deallocate it like
a normal array, remembering to use an appropriately typed pointer.
Object-typed lists are restricted (when used with @{b }NEW@{ub }) to an array of at
most one object, so is useful only for allocating an initialised object
(not really an array). Notice how, in the following code, the pointer @{b }q@{ub }
can be treated both as an object and as an array of one object (see
@{"Element selection and element types" Link "Types.guide/Element selection and element types" }).
OBJECT myobj
x:INT, y:LONG, z:INT
ENDOBJECT
PROC main()
DEF p:PTR TO INT, q:PTR TO myobj
p:=NEW [1, 9, 3, 7, 6]:INT
q:=NEW [1, 2]:myobj
WriteF('Last element in array p is \\d\\n', p[4])
WriteF('Object q is x=\\d, y=\\d, z=\\d\\n',
q.x, q.y, q.z)
WriteF('Array q is q[0].x=\\d, q[0].y=\\d, q[0].z=\\d\\n',
q[].x, q[].y, q[].z)
END p[5], q
ENDPROC
The dynamically allocated version of an object-typed list differs from the
static version in another way: it always has memory allocated for a whole
number of objects, so a partially initialised object is padded with zero
elements. The static version does not allocate this extra padding, so you
must be careful not to access any element beyond those mentioned in the
list.
The deallocation of @{b }NEW@{ub } copies of normal lists can, as ever, be left to
be done automatically at the end of the program. If you want to
deallocate them before this time you must use the function
@{b }FastDisposeList@{ub }, passing the address of the list as the only argument.
You @{i }must@{ui } not use @{b }END@{ub } or any other method of deallocation. @{b }FastDisposeList@{ub }
is the only safe way of deallocating lists allocated using @{b }NEW@{ub }.
@ENDNODE
@NODE "OOP object allocation" "OOP object allocation"
@Prev "List and typed list allocation"
@Toc "NEW and END Operators"
OOP object allocation
---------------------
Currently, the only way to create OOP objects in E is to use @{b }NEW@{ub } and
the only safe way to destroy them is to use @{b }END@{ub }. This is probably the
most common use of @{b }NEW@{ub } and @{b }END@{ub } and is described in detail later (see
@{"Objects in E" Link "OOE.guide/Objects in E" }).
@ENDNODE