Names, binding, lifetime and scope (static vs dynamic)
The six attributes of a variable, the binding time table, the four storage categories, the difference between scope and lifetime, and tracing program output under static/dynamic scope.
This section has two faces. The short-answer part: the binding time table and storage categories, memorize and done. The long part: program output under static and dynamic scope. This question is almost certain on the midterm and is solved by building a table.
Golden rules:
- Static scope = look at the page. If a variable is not local, it is searched for in the unit enclosing where the function is written.
- Dynamic scope = look at the invitation. If a variable is not local, it is searched for in the calling function, then in its caller.
- If a nonlocal variable is assigned to, the variable you found changes. When you return to the caller you see the new value.
- Scope ≠ lifetime. Visibility is about the text, lifetime is about the execution.
The six attributes of a variable
| Attribute | Meaning |
|---|---|
| Name | The name that denotes the variable. Design questions: case sensitivity, length limit, reserved words |
| Address | Its place in memory, the l-value. The same name can be at different addresses at different times (recursion) |
| Value | The content of the cell, the r-value |
| Type | The range of values and the operations that can be done |
| Lifetime | The time it stays bound to the cell |
| Scope | The range of statements where it is visible |
If two names access the same address, they are aliases. Pointers, references and unions in C create aliases. A reader may not notice that the value of a variable was changed through another name: reliability drops.
Binding and binding time
Binding is the association between an entity and an attribute: a variable and its type, a name and its address, a symbol and an operation. Binding time is the moment when this association is made.
| Binding time | Example |
|---|---|
| Language design time | Binding the symbol * to multiplication, the meaning of while |
| Language implementation time | The size and range of int in C, the floating-point representation |
| Compile time | Binding a variable to its type in C or Java |
| Link time | Binding a library function call to the function’s code |
| Load time | Binding a C global or static variable to a memory cell |
| Run time | Binding a non-static local variable to a cell; binding a value to a variable |
Static binding happens before execution and does not change during execution. Dynamic binding happens during execution or can change.
Type binding
- Explicit declaration:
int count; - Implicit declaration: by a naming rule. In Fortran, a name starting with I-N is integer, the others real. In Perl,
$starts a scalar,@an array. - Type inference: the type is inferred from context.
var sum = 0;in C#, almost everything in ML and Haskell. - Dynamic type binding: the type is bound at assignment (Python, JavaScript, Ruby, PHP).
let t = 5;
t = "five";
console.log(typeof t);
string
Dynamic type binding gives flexibility (the same code works with different types) but the compiler cannot catch type errors, and run-time type checking is expensive; most of these languages are interpreted.
Lifetime and storage categories
Getting a cell is called allocation, giving it back is called deallocation. Lifetime is the time between the two.
| Category | When bound | Example | Pros / cons |
|---|---|---|---|
| Static | Before execution, until the program ends | C global, C static local, Java static field | Fast, direct addressing, remembers history / no recursion |
| Stack-dynamic | When the declaration is elaborated, type static | C/Java method locals | Recursion, memory sharing / allocation cost, indirect addressing |
| Explicit heap-dynamic | By an explicit command: new, malloc | Java objects, C++ new | Linked lists, trees / pointer errors, management difficulty |
| Implicit heap-dynamic | Automatically when a value is assigned | JavaScript arrays, Python variables | Most flexible / inefficient, loss of error detection |
PITFALL · a pointer and what it points to are in different categories. In the line
int *p = malloc(sizeof(int));, p itself is stack-dynamic (a local variable), and the cell it points to is explicit heap-dynamic.
Scope and lifetime are not the same thing
void count() {
static int calls = 0;
calls++;
printf("%d ", calls);
}
/* count(); count(); count(); */
1 2 3
The scope of calls is only the body of count, but its lifetime is the whole program: the value is kept between calls. The reverse also happens: when f calls g, f’s local variables are still alive but (under static scope) not visible from inside g.
Scope
If a variable is used in a statement and not declared in that unit, it is nonlocal. The scope rule determines where to look for it.
Static (lexical) scope
Look at the program text. First the local declarations, then the unit enclosing where the function is written (the static parent), then its parent (the static ancestors). Determined at compile time. C, Java, Python, JavaScript, Ada.
An inner declaration hides an outer variable with the same name. Blocks { ... } open their own scope:
{ var v = 1; { var v = 2; } console.log(v); }
{ let w = 1; { let w = 2; } console.log(w); }
2
1
In JavaScript, var does not recognize block scope, let does. In Java and C#, an inner block cannot redeclare the name of a local variable in the same method (compile error: variable x is already defined); in C and C++ it can, and it hides the outer one.
Dynamic scope
Look at the call chain. First the local, then the function that called me, then the one that called it… Determined at run time. Early LISP, APL, SNOBOL4 and Perl’s local declaration.
The criticism: a function’s local variables are exposed to every function it calls (reliability drops), what a nonlocal reference refers to depends on the call path (readability drops), static type checking is not possible, and access is slow. The only advantage: the caller’s variables can be used without passing parameters.
Worked example: a trace table
int n = 1;
void a() { n = n * 10; print(n); }
void b() { int n = 2; a(); }
void c() { int n = 3; b(); a(); print(n); }
main() { c(); print(n); }
The n in a is nonlocal. Trace the two rules separately:
STATIC: a's static parent is global → the n in a is always the global n
event global n c.n b.n output
c → b → a 1 → 10 3 2 10
c → a 10 → 100 3 100
c: print(n) 3 3
main 100 100
DYNAMIC: no n in a → look at the caller
event n searched, where found new value output
c → b → a b.n (2) 20 20
c → a c.n (3) 30 30
c: print(n) c's own n 30 30
main global n (untouched) 1 1
Static: 10 100 3 100. Dynamic: 20 30 30 1. These two outputs can be reproduced exactly in Perl by using local instead of my.
PITFALL · looking at the definition in dynamic scope. In dynamic scope where the function is written is irrelevant; the only question is “who called me right now?” And the nearest caller wins: in the chain main → c → b → a, b’s n is found before c’s.
Referencing environment
The referencing environment of a statement is all the names visible there. Under static scope: the locals + the unhidden static ancestor variables. Under dynamic scope: the locals + the unhidden variables of all active callers.
function main() { var a, b, c;
function sub1() { var b, d;
function sub2() { var c, e; /* point 2 */ } }
function sub3() { var a, d; /* point 3 */ } }
point 2: c, e (sub2) · b, d (sub1) · a (main)
point 3: a, d (sub3) · b, c (main) sub1's variables are NOT there
Common mistakes
| Wrong | Correct |
|---|---|
| Looking at where the function is written in dynamic scope | Only the call chain |
| Taking the outermost in the chain instead of the first found | The nearest caller wins |
| Creating a new local variable on assignment to a nonlocal | The variable found is changed |
| Scope = lifetime | C static local: scope function, lifetime program |
p = malloc(...) → p is heap-dynamic | p is stack-dynamic, the cell it points to is heap-dynamic |
| C static variable → compile time | Load time |
| Library call → compile time | Link time |
| Listing the locals of a sibling function at point 3 | A sibling is not a static ancestor |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed (a) under static scoping and (b) under dynamic scoping?
int x = 5;
void f() { print(x); }
void g() { int x = 7; f(); }
main() { g(); f(); }Show answer
The x in f is not local (nonlocal). Static: f is written at the global level in the text, its static parent is global → 5 in both calls. Dynamic: in the first call the chain is main → g → f; f has no x, the caller g has one: 7. The second call is main → f: global 5.
What is printed (a) under static scoping and (b) under dynamic scoping?
int n = 1;
void a() { n = n * 10; print(n); }
void b() { int n = 2; a(); }
void c() { int n = 3; b(); a(); print(n); }
main() { c(); print(n); }Show answer
Static: the n in a is always the global n. c → b → a: global 1 → 10, print 10. c → a: 10 → 100, print 100. c prints its own n: 3. main prints the global: 100.
Dynamic: c → b → a: the nearest n is b's (2) → 20, print 20. c → a: the nearest n is c's (3) → 30, print 30. c prints its own n: a changed it, 30. The global n was never touched: 1.
Give the complete output (a) under static scoping and (b) under dynamic scoping.
function main() {
var a = 1, b = 10;
function p() { a = a + b; print(a, b); }
function q() { var b = 20; p(); r(); }
function r() { var a = 100; p(); print(a); }
q();
print(a, b);
}Show answer
Static: p, q and r are all written inside main, so the a and b in p are always main's. q → p: main.a = 1 + 10 = 11 → "11 10". q → r → p: main.a = 11 + 10 = 21 → "21 10". r prints its own a: 100. main: "21 10".
Dynamic: q → p: look up a, not in q → main.a; look up b → q.b = 20. main.a = 21 → "21 20". q → r → p: a → r.a (100), b → q.b (20): r.a = 120 → "120 20". r prints its own a, which had changed: 120. main: main.a 21, main.b 10 → "21 10".
Assuming static scoping, which declaration does each name refer to at point 2?
function main() {
var a, b, c;
function sub1() {
var b, d;
function sub2() {
var c, e;
/* point 2 */
}
}
function sub3() {
var a, d;
/* point 3 */
}
}Show answer
First the locals (sub2: c, e), then the static parent sub1 (b, d), then main (a; main.b and main.c are hidden). At point 3: a, d are from sub3; b, c are from main. The variables of sub1 are not visible from sub3 because sub3 is not inside sub1.
Give the binding time of each: (a) the meaning of the reserved word while; (b) the size of long in C; (c) the type of a local variable in Java; (d) the address of a C global variable; (e) the type of a Python variable.
Show answer
Order: design → implementation → compile → link → load → run. The meaning of while is decided by whoever designs the language. The C standard does not fix the size of long; the compiler/platform decides. In Java the type declaration is read by the compiler (static type binding). A global variable gets its address when the program is loaded into memory. In Python the type is bound at assignment (dynamic type binding).
Classify the storage category of each: (a) a Java static field; (b) a local int inside a Java method; (c) the object created by new int[5] in Java; (d) a JavaScript variable v = [1, 2, 3].
Show answer
Static: bound before execution, lives until the program ends. Stack-dynamic: allocated on the stack when its declaration is elaborated, gone when the method returns. Explicit heap-dynamic: on the heap by the programmer's explicit command (new), accessed through a reference. Implicit heap-dynamic: allocated automatically when a value is assigned, and the type is bound at that moment too.
In C, the function below is called three times. What is printed, and what are the scope and the lifetime of calls?
void count() {
static int calls = 0;
calls++;
printf("%d ", calls);
}Show answer
A static local variable is allocated and initialized once, and keeps its value between calls (history sensitive). It can be accessed by name only from inside count (scope), but the cell lives until the program ends (lifetime). The classic example that scope and lifetime are different.
Does this Java code compile? Would the equivalent C code compile?
public static void main(String[] args) {
int x = 1;
{ int x = 2; System.out.println(x); }
}Show answer
Java and C# do not allow an inner block to redeclare the name of a local variable in the same method: "variable x is already defined in method main". In C and C++ the inner block opens a new scope and hides the outer x.
What do the two console.log calls print in JavaScript?
{ var v = 1; { var v = 2; } console.log(v); }
{ let w = 1; { let w = 2; } console.log(w); }Show answer
var has function (or global) scope; a block does not open its own scope: the two v are the same variable, the last value is 2. let has block scope: the inner w is a separate variable, and the outer stays 1.
In Fortran's implicit declaration rule, what are the types of KOUNT, TOTAL and NUM?
Show answer
In Fortran an undeclared name is considered integer if it starts with I, J, K, L, M or N, otherwise real. KOUNT (K) and NUM (N) are integer, TOTAL (T) is real. Implicit declaration makes writing easier but hides typing mistakes (reliability drops).
In the assignment x = x + 1, what does the x on the left denote and what does the x on the right denote?
Show answer
The name on the left of an assignment asks for the address of the cell (l-value), and the value is written there. The name on the right reads the value in the cell (r-value). If the same address can be accessed with two different names, they are called aliases.
What is printed in JavaScript? Which kind of type binding does this show?
let t = 5;
t = "five";
console.log(typeof t);Show answer
In JavaScript the type of a variable is bound not by a declaration but by the assigned value, at run time, and it can change. The upside is flexibility; the downside is that the compiler cannot catch type errors and run-time type checking has a cost.
Verification: Static scope outputs were actually run with JavaScript's own lexical rule (node verify/SEN2001/04-static.mjs), dynamic scope outputs in Perl with local (perl verify/SEN2001/04-dynamic.pl). The referencing environment was found by giving each declaration its own name as its value and printing with node. The C static local variable example was checked with a Java static field (java W04.java), the Java block rule with the compiler (java W04b.java gave a compile error).