Scope
Suppose that a name is used many times for different entities in text of the program, or in the course of execution. When the name is used, to which of these entities does it refer?
Types of names
• Variables • Constants• Statement labels• Functions/procedures• Types• Macros• Exceptions• Modules (packages, files, etc.)
Postponing
• Names of modules and references between modules (to lecture on modules)
• Variables used in functions passed as parameters and returned as values (to lecture on functional programming)
• Exceptions (to lecture on exceptions).
Easy cases
• Types and macros are (as far as I know) global to a module.
• Statements labels are scoped within the function, and are visible in that functions and in lexically contained functions.
Function names
In non-hierarchical languages (e.g. C), function names are global to the module.
Some languages require that a function be declared lexically before it is used; others do not.
If you impose this rule for mutually recursive functions, then there must be declarations that are separate from definition.
Function names: Hierarchical languages
Closest nested scope:
Suppose that function C is lexically inside B which is lexically inside A, and C calls function F.
In order look for a declaration of F immediately inside first C, then B, then A, then global. Otherwise, the reference is invalid.
Example (Pascal)function A() function F() begin … end // Body of F1 function B() function F() begin … end // Body of F2
function C() begin … F() … end // Body of C. Call to F2
begin … F() … end // Body of B. Call to F2
function D() begin … F() … end // Body of D. Call to F1
begin … F() … end // Body of A. Call to F1
Scope of Function Name
Nothing particularly interesting has to be done in terms of the compiled code. The compiler uses the scope rule to assign the reference, but the calling and returning protocols are unaffected.
Scope of Variables
• Static scope (also called “lexical scope”). As with functions, use the nearest containing scope that declares the variable. Note: The reference can be determined at compile time.
• Dynamic scope. Use the most recent declaration in the calling sequence
Static and dynamic scoping: Example
int N;void main() {N=1; A(); printf(“main: %1d”,N);}
void A()int N;{ N = 2; B(); printf(“A: %1d . ”, N); }
void B() { N = 3; }
Static scoping: Prints “A: 2 . main: 3” (I in B is global)Dynamic scoping: Prints “A: 3 . main: 1” (I in B is I in A)
Implementing static scoping: no function imbedding
Trivial: Variables are either local (in current activation record) or global (fixed address).
Implementing static scoping:function imbedding
• Each activation record on the stack has a static link to the activation record of the most recent call to the routine that lexically contains it.
• To resolve a reference to a variable in a routine K levels out from the current routine, follow the chain of static links K steps to the proper activation record, and then go the appropriate offset.
• Number of links and offset are known at compile time.
function A() { int I;
function B() { int J;
function C() { int K;
K=I+J; B(); }
C(); } // body of B.
B(); } // body of A.
A calls B calls C calls
B calls C.
Setting static links in calling protocol
• If F calls G and G is immediately lexically contained in H, then (by the scoping rules for functions) either – F=H, in which case the static link from G points to the
static link in F’s activation record– or F is lexically contained in H (not necessarily
immediately), in which case the link to H is a known number of steps down the static chain from F.
• In either case, the compiler can determine how F should set the static link for G.
Implementing dynamic scoping:symbol table
Symbol table: For each variable name X, keep a pointer to the most recent version of X.
If function F declares X, then F must store a pointer to the previous version of X.
On entering routine F, push a pointer to previous location of X onto stack,
On exiting F, restore previous value of X to symbol table.
Example
function A() {
int I,J; B(); }
function B() {
int J,K; C(); }
function C() {
int I,K B(); }
A calls B calls C calls B.
Declarations and definitions
• Is declaration separate from definition?
• If a name is declared, is the scope the entire context, or only after it is declared?
• Can a name be used before it is declared?
Particularly important for mutually recursive types and function definitions.
In weird cases, it can be important for constant definitions.
Mutually recursive functions: Pascal
forward procedure B(I: integer) // declaration
procedure A(I: integer) // declaration and
begin B(I-1) end // definition
procedure B(I: integer) // definition
begin A(I-1) end
Mutually recursive functions: C
void B(int I); // declaration
void A(int I) { B(I-1); } // declaration and // definition
void B(int I) { A(I-1); } // definition
Ada is similar.
Recursive types: Pascal
In defining a pointer type P, you can refer to another type T before T has been declared (because nothing about P, such as the size, actually depends on T).
type PRED = ^RED;PBLACK = ^BLACK;RED = record V,D: Integer; L,R: PBLACK end;BLACK = record V,D: Integer; L,R: PRED end;
Recursive types: C
Separate declaration from definition, like functions.
struct RED;
struct BLACK { int V, P; struct RED *L, *R; }
struct RED { int V,P; struct BLACK *L, *R; }