Storage Allocation Strategies
Storage Allocation Strategies
Storage Allocation Strategies
The storage allocation strategy used in each of the three data areas,
namely static data area, heap, stack, are different.
Static allocation lays out storage for all data objects at compile time.
Stack allocation manages the run-time storage as a stack.
Heap allocation allocates and deallocates storage as needed at runtime from a data
area known as heap.
STATIC ALLOCATION:
In static allocation, names are bound to storage as the program is
compiled, so there is no need for a run-time support package. Since the bindings do not
change at run time, every time a procedure is activated, its names are bounded to the
same storage locations. This property allows the values of the local names to be retained
during activations of a procedure.
From the type of a name, the compiler determines the amount of
storage to set aside for that name.. The compiler must eventually decide where the
activation records go, relative to the target code and to one another. Once this decision is
made, the position of each activation record, and hence of the storage for each name in
the record is fixed. At compile time we can therefore fill in the addresses at which the
target code can find the data it operates on. Similarly the addresses at which information
is to be saved when a procedure call occurs are also known at compile time.
However, some limitations go along with using static allocations
alone.
1. The size of the data object and constraints on its position in memory must be known at
compile time.
2. Recursive procedures are restricted, because all activations of a procedure use the same
bindings for local names.
3. Data structures cannot be created dynamically, since there is no mechanism for storage
allocation at run time.
record for CNSUME, there is space for the locals BUF, NEXT, and C. The storage bound
to BUF holds a string of 50 characters. It is followed by space for holding an integer
value for NEXT and a character value for C. The fact that NEXT is also declared in
PRDUCE presents no problem, because the locals of the two procedures get space in
their respective activation records.
Since the sizes of the executable code and the activation records are
known at compile time, On some computer systems, it is feasible to leave the relative
position of the activation records unspecified and allow the link editor to link activation
records and executable code.
STACK ALLOCATION:
Stack allocation is based on the idea of control stack; storage is organized
as a stack, and activation records are pushed and popped as activations begin and end,
respectively. Storage for the locals in each call of a procedure is contained in the
activation record for that call. Thus locals are bound to fresh storage in each activation,
because a new activation record is pushed onto the stack when a call is made.
Furthermore, the values of locals are deleted when the activation ends; that is, the values
are lost because the storage for locals disappears when the activation record is popped.
Consider the case in which the sizes of all activation records are known at
compile time. Suppose that register top marks the top of the stack. At run time, an
activation record can be allocated and deallocated by incrementing and decrementing top,
respectively, by the size of the record. If procedure q has an activation record of size a,
then top is incremented by a just before the target code of q is executed. When control
returns from q, top is decremented by a.
Calling sequences
Procedure calls are implemented by generating what are known as calling
sequences in the target code. A call sequence allocates an activation record and enters
information into its fields. A return sequence restores the state of the machine so the
calling procedure can continue execution.
Calling sequences and activation records differ, even for implementations
of the same language. The code in a calling sequence is often divided between the calling
procedure and the procedure it calls. There is no exact division of run-time tasks between
the caller and the callee the source language, the target machine and the operating
system impose requirements that may favor one solution over another.
A principle that aids the design of calling sequences and activation records
is that fields whose sizes are fixed early are placed in the middle. The decision about
whether or not to use control and access links is part of the design of the compiler, so
these fields can be fixed at compiler-construction time. If exactly the same amount of
machine-status information is saved for each activation, then the same code can do the
saving and restoring for all activations. Moreover programs such as debuggers will have
an easier time deciphering the stack contents when an error occurs.
Even though the size of the field for temporaries is eventually fixed at
compile time, this size may not be known to the front end. Careful code generation or
optimization may reduce the number of temporaries needed by the procedure, so as far as
the front end is concerned, the size of this field is unknown. In the general activation
record, we therefore show this field after that for local data, where changes in its size will
not affect the offsets of data objects relative to the fields in the middle.
Since each call has its own actual parameters, the caller usually evaluates
actual parameters and communicates them to the activation record of the callee. In the
run-time stack, the activation record of the caller is just below that for the callee as in
figure(d). There is an advantage to placing the fields for parameters and a potential
returned value next to the activation record of the caller. The caller can then access these
fields using offsets from the end of its own activation record, without knowing the
complete layout of the record for the callee. In particular, there is no reason for the caller
to know about the local data or temporaries of the callee. A benefit of this information
hiding is that procedures with variable numbers of arguments, such as printf in C, can be
handled.
The following call sequence is motivated by the above discussion. As in
figure(d), the register top_sp points to the end of the machine-status field in an activation
record. This position is known to the caller, so that it can be made responsible for setting
top_sp before control flows to the called procedure. The code for the callee can access its
temporaries and local data using offsets from top_sp. The call sequence is:
1. The caller evaluates actuals.
2. The caller stores a return address and the old value of top_sp into the caller's
activation record. The caller the increments top_sp to the position shown in
figure(d). That is, top_sp is moved past the callers local data and temporaries and
the caller parameter and status fields.
3. The callee saves register values and other status information.
4. The callee initializes its local data and begins execution.
A possible return sequence is:
1. The callee places a return value next to the activation record of the caller.
2. Using the information in the status field, the callee restores top_sp and other
registers and branches to the return address in the callers code.
3. Although top_sp has been decremented, the caller can copy the returned value
into its own activation record and use it to evaluate an expression.
The calling sequences allow the number of arguments of the called
procedure to depend on the call. At compile time, the target code of the caller knows
the number of arguments it is supplying to the callee. Hence the caller knows the size
of the parameter field. However, the target code of the callee must be prepared to
handle other calls as well, so it waits until it is called, and then examines the
parameter field. Using the organization in figure(d), information describing the
parameters must be placed next to the status field so the callee can find it.
Variable-length data
A common strategy for handling variable-length data is suggested in
figure(e), where procedure p has two local arrays. The storage for these arrays is not part
of the activation record for p; only a pointer to the beginning of each array appears in the
activation record. The relative addresses of these pointers are known at compile time, so
the target code can access array elements through the pointers.
Also shown in figure(e) is a procedure q called by p. The activation record
for q begins after the arrays of p, and the variable length arrays of q begin beyond that.
Access to data on the stack is through two pointers, top and top_sp. The
first of these marks the actual top of the stack; it points to the position at which the next
activation record will begin. The second is to find local data. For consistency with the
organization of figure(d), suppose top_sp points to the end of the machine-status field. In
figure(e), top_sp points to the end of this field in the activation record for q. within the
field is a control link to the previous value of top_sp when control was in the calling
activation of p.
The code to reposition top and top_sp can be generated at compile time,
using the sizes of the fields in the activation records. When q returns, the new value of
top is top_sp
minus the length of the machine-status and parameters field in qs
activation record. This length is known at compile time, at least to the caller. After
adjusting top the new value of top_sp can be copied from control link of q.
DANGLING REFERENCES:
Whenever storage can be deallocated, the problem of dangling references
arises. A dangling reference occurs when there is a reference to storage that has been
deallocated. It is a logical error to use dangling references, since the value of deallocated
storage is undefined according to the semantics of most languages. Worse, since that
storage may later be allocated to another datum, mysterious bugs can appear in programs
with dangling references.
HEAP ALLOCATION:
The stack allocation strategy cannot be used if either of the following is
possible:
1. The values of local names must be retained when activation ends.