Binding Times ------------- Binding time - the moment when a symbol gets a value (its meaning or interpretation) Examples of binding times ------------------------- run time (assignment of a memory location to a variable, e.g., local variable in subroutine; assignment of a value to that location) compile time (programmer assigns a type to a variable name, the compiler implements multidimensional arrays in a particular way, loader fills addresses in place of subroutine names, etc.) language implementation time (the representation of integers) language definition time (choice of keywords and symbols, and their meanings) Note that some symbols in a language do not get their precise meaning until compile time or run time. example: +. Tradeoffs in time of binding ---------------------------- early binding---efficient late binding---flexible (can be data dependent at run time) Data objects have a life ------------------------ Data objects exist as patterns in memory. They have a lifetime, and bindings have lifetimes. Approaches to storage allocation 1. static, as in original Fortran (no recursive subroutines) data associated with subroutines: arguments, return values, temporary variables added by compiler, local variables (if recursion is not allowed), bookkeeping info like return address, saved registers 2. stack, as in languages that allow recursive subroutines requires extra code before and after subroutine call (calling sequence), extra code before body of called subroutine (prologue), and extra code after body of called subroutine (epilogue) The part of a stack created for each subroutine call is called a frame, or activation record. The data associated with that subroutine call (enumerated above under static allocation) is in the frame. 3. heap, for dynamically allocating memory for data that will be passed from subroutine to subroutine and changed is size from time to time; a free list of available blocks of storage is maintained. Common storage management algorithms include first fit, best fit, next fit. Others are the buddy system and the Fibonacci heap. Fragmentation can occur with these algorithms. Internal fragmentation occurs when a block is larger than is needed by the data it holds; some memory is wasted. External fragmentation occurs when the blocks in the heap get so small that none are big enough to hold the required data, but there would have been enough memory if all the free space were in one block. dangling references garbage collection compaction What does it name? ------------------ Identifiers can name many different kinds of program elements: variables formal parameters subprograms types constants statements (labels) exceptions Furthermore, the same identifier may name different things in different parts of the program. What an identifier names is determined by binding information in the referencing environment. Global, local, nonlocal referencing environments. ----------------------- Part of the global referencing environment may be predefined. A binding is visible in a referencing environment if it determines what ------- the identifier names. A binding may become hidden if another binding for the same identifier ------ takes precedence. Example: subprogram call may redefine an identifier while it runs. More than one identifier may be bound to the same value at the same time and both may be visible in the referencing environment. Such identifiers are called aliases. ------- The problem with aliases is that programs are hard to understand; a data object may be changed through one reference, but we may not notice that that change is visible through another reference. Static and dynamic scope ------------------------ The scope of a variable is the set of program statements in which the variable's binding is visible. Dynamic scope -- the set of program statements is determined by the nesting of subprogram calls at run time. procedure A int x; procedure B real x; ... C; ... procedure C ... y := x + 1; ... ... C; ... The two calls to C see different bindings for x. Problems with dynamic scoping: (i) local variables are always visible to called subprograms (unless local variables in the called subprograms have the same name) (ii) types of nonlocal variables cannot be statically checked because it is hard to find the applicable variable declaration. (iii) programs are difficult to read because the reader has to keep track of the nesting of subprogram calls. (iv) runtime access to the value of nonlocal variables generally take longer than with static scoping. Lisp used to have dynamic scoping, but modern lisp, especially Common Lisp has static scope (although dynamic scoping can be activated by special declarations). Static scoping -- the set of statements in which variable bindings are visible can be determined by looking at the program text. Most languages provide for static scoping by having block structure. --------------- Note: the example described at the bottom of page 369 is an implementation of the static scope rule, not dynamic scope rule. Initialization can be handled in two ways: 1. the variable can be initialized once; the value of the variable is retained after a subprogram call and is available again the next time the subprogram is called. Variables are static. 2. the variable is initialized each time the subprogram is called. Variables are dynamic. Static variables provide history sensitivity; classes allow us to have multiple copies of similar static variables (each instance of a given class). Module -- cluster of subroutines with their own private "global" variables. Class -- module interpreted as a data type so that several instances can exist at the same time. Also adds inheritance to module concept. Symbol tables ------------- Needed during compile time or run time to keep track of which bindings are in effect at any given moment. For dynamic scope: Used at run time. 1. An association list holds all the current bindings. Bindings in a given scope are pushed on and off the list as scope is entered and exited. To find the binding for a given name, list is searched; the first key-value pair that is found where the key matches the name is the binding currently in effect for that name. 2. A central reference table can be used. Each name has a place in the table where there is a linked list of all the bindings for that name. The first binding in the list is the current binding for that name. Faster than the association list approach because the table entry can be found in one pointer reference or one hash, if it is a hash table. New bindings are pushed onto the appropriate linked lists as a new scope is entered and popped of on exiting scope. For static scope: Used at compile time. LeBlanc-Cook symbol table. Explain Figs 3.14 and 3.13. In some languages, modules have closed scope; nothing outside them can be referenced if they are not imported first. (Importing causes table entry at new scope level that points to old entry at old scope level.)