After the analysis phases are complete, the compiler must generate executable code. The generated code must include routines for managing the changing data as the program executes. Depending on the language, the runtime environment may be: fully static stack-based dynamic The type of environment determines whether we need to use a stack, a heap, or both.
entry address
free space
heap
activation record : functionX par1 (type) : <value> ... parN (type): <value> start code ptr: <ptr> current ptr: <ptr> return address: <ptr> var1 (type) : <value> ... varN (type): <value>
Each time the flow of control enters a function or procedure, we update its procedure activation record.
This maintains the values of the function arguments and all local variables defined inside the function, and pointers to the start of the code segment, the current location in the code segment, and the segment of code to which we return on exit.
A fully static environment (FORTRAN77) has no recursively-called procedures no pointers no dynamic memory allocation This means that only one copy of each procedure activation record can be active at any one time.
Therefore, we store only one copy, and we can allocate storage at compile time.
Each time we enter a procedure, we simply update the fields of its activation record.
Static example
int i = 10;
2 3 4 5 6 7
global area i (int) : activation record : main start code ptr: current ptr: return address: k (int) : activation record : f1 j (int) : start code ptr: current ptr: return address: k (int) :
Stack-based Environment
Stack-based languages do allow procedures to be called recursively. Typically, they also allow dynamic memory allocation and pointers to data locations. Since procedures can be called recursively, we may need to hold a number of different records for the same function, representing each invocation. Activation records will be created as required and placed on the stack. Each record will maintain a pointer to the record that activated it, and on completion, the current record will be deleted from the stack, and control passed back to the calling record.
Stack-based Example
1 2 3 4 5 6 7 8 9 int x, y; int gcd(int u, int v) { if (v == 0) return u; else return gcd(v, u % v); } main() { scanf("%d %d", &x, &y); printf("%d\n", gcd(x, y)); } on 1st entry to gcd global area x (int): 15 initial environment y (int): 10 global area x (int): y (int): fp activation record: main start ptr: 6 sp current ptr: activation record: main start ptr: 6 current ptr: 8 fp activation record: gcd u (int): 15 v (int): 10 start ptr: 2 current ptr: 2 return ptr: sp return address: 8
on 3rd entry to gcd global area x (int): 15 y (int): 10 activation record: main start ptr: 6 current ptr: 8 activation record: gcd u (int): 15 v (int): 10 start ptr: 2 current ptr: 4 return ptr: return address: 8 activation record: gcd u (int): 10 v (int): 5 start ptr: 2 current ptr: 4 return ptr: return address: 4 activation record: gcd u (int): 5 v (int): 0 start ptr: 2 current ptr: 2 return ptr: return address: 4
fp sp
fp
sp
Dangling References
int *dangle() { int x = 3; return &x; } int inc(int y) { int z; z = y + 1; return z; } main() { int *w, t; w = dangle(); printf("w = %d\n", *w); t = inc(10); printf("w = %d\n", *w); } % a.out w = 3 w = 11 %
Dynamic Environments
Returning the address of a local variable is defined to be a logical error in languages like C.
In a dynamic environment there is no such restriction - all variables and activation records must be maintained for as long as there are references to them. It is also possible to return pointers to local functions.
Dynamic environments must deallocate space when procedures and variables can no longer be reached - garbage collection .
It is normal to use a heap to maintain such records. Two functions are required:
allocate - returns the address of a memory block of the appropriate size free - marks a block as being free for re-use
Heap Management
Stack-based languages like C also require a heap to maintain pointer allocation and dynamically allocated memory. C allocate free malloc free C++ new delete Java new
The two main challenges in heap management are: stopping the heap becoming fragmented, by combining successive freed blocks into one ensuring that free is only ever applied to the start of a block of the required size
circular linked list of allocated memory blocks each block records its used size, the size of the free space after it, and points to the next block the block at the top of the heap points to a block with some free space after it to allocate a block find a block with enough free space after it jump to end of that block's used space insert the new block into the heap compute the reduced free space set the previous block's free space to 0 update the list to free a block move to top of the heap step through list of blocks until required address add the block's free size and used size to the free size of its previous block
header last next next us:15 fs:0 used next us:10 fs:0 used next us:10 fs:20 used
0 5
0 5
20
30
40
next us:10 fs:0 used next us:10 fs:0 used next us:15 fs:5 used
20
30
40
free
60
free
55
60
20 free next us:10 fs:0 used next us:15 fs:5 used free 30 free
20
30
40
40
55
60
free
55
60