SEN2001 · Week 4 · Names, Bindings, Type Checking and Scopes

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.

namevariablel-valuer-valuealiasbindingbinding timestatic bindingdynamic bindingtype inferencelifetimestatic variablestack-dynamicexplicit heap-dynamicimplicit heap-dynamicscopestatic scopedynamic scopestatic parentreferencing environmentblock

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:

  1. 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.
  2. Dynamic scope = look at the invitation. If a variable is not local, it is searched for in the calling function, then in its caller.
  3. If a nonlocal variable is assigned to, the variable you found changes. When you return to the caller you see the new value.
  4. Scope ≠ lifetime. Visibility is about the text, lifetime is about the execution.

The six attributes of a variable

AttributeMeaning
NameThe name that denotes the variable. Design questions: case sensitivity, length limit, reserved words
AddressIts place in memory, the l-value. The same name can be at different addresses at different times (recursion)
ValueThe content of the cell, the r-value
TypeThe range of values and the operations that can be done
LifetimeThe time it stays bound to the cell
ScopeThe 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 timeExample
Language design timeBinding the symbol * to multiplication, the meaning of while
Language implementation timeThe size and range of int in C, the floating-point representation
Compile timeBinding a variable to its type in C or Java
Link timeBinding a library function call to the function’s code
Load timeBinding a C global or static variable to a memory cell
Run timeBinding 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.

CategoryWhen boundExamplePros / cons
StaticBefore execution, until the program endsC global, C static local, Java static fieldFast, direct addressing, remembers history / no recursion
Stack-dynamicWhen the declaration is elaborated, type staticC/Java method localsRecursion, memory sharing / allocation cost, indirect addressing
Explicit heap-dynamicBy an explicit command: new, mallocJava objects, C++ newLinked lists, trees / pointer errors, management difficulty
Implicit heap-dynamicAutomatically when a value is assignedJavaScript arrays, Python variablesMost 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

WrongCorrect
Looking at where the function is written in dynamic scopeOnly the call chain
Taking the outermost in the chain instead of the first foundThe nearest caller wins
Creating a new local variable on assignment to a nonlocalThe variable found is changed
Scope = lifetimeC static local: scope function, lifetime program
p = malloc(...) → p is heap-dynamicp is stack-dynamic, the cell it points to is heap-dynamic
C static variable → compile timeLoad time
Library call → compile timeLink time
Listing the locals of a sibling function at point 3A sibling is not a static ancestor

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

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
(a) 5 5 (b) 7 5

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.

Question 2medium

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
(a) 10 100 3 100 (b) 20 30 30 1

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.

Question 3hard

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
(a) 11 10 / 21 10 / 100 / 21 10 (b) 21 20 / 120 20 / 120 / 21 10

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".

Question 4medium

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
a: main, b: sub1, c: sub2, d: sub1, e: sub2

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.

Question 5medium

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
(a) language design (b) language implementation (c) compile time (d) load time (e) run time

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).

Question 6medium

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
(a) static (b) stack-dynamic (c) explicit heap-dynamic (d) implicit heap-dynamic

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.

Question 7medium

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
1 2 3; scope: the body of count; lifetime: the whole program run

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.

Question 8medium

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: no (x is already defined); C: yes, the inner x hides the outer one

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.

Question 9medium

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
2 and 1

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.

Question 10easy

In Fortran's implicit declaration rule, what are the types of KOUNT, TOTAL and NUM?

Show answer
KOUNT: integer, TOTAL: real, NUM: integer

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).

Question 11easy

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
left: the l-value (address); right: the r-value (value)

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.

Question 12easy

What is printed in JavaScript? Which kind of type binding does this show?

let t = 5;
t = "five";
console.log(typeof t);
Show answer
string; dynamic type binding

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).

Review card

6 attributes of a variable: name, address (l-value), value (r-value), type, lifetime, scope alias: two names for the same address (reliability drops) binding time: design → implementation → compile → link → load → run * = multiplication: design | C int size: impl | Java type: compile | library: link C static/global cell: load | local cell, value: run type binding: explicit / implicit (Fortran I-N) / inference (var, ML) / dynamic (Python, JS) storage: static | stack-dynamic | explicit heap (new) | implicit heap (at assignment) scope ≠ lifetime: C static local → scope function, lifetime program STATIC scope = look at the text: local → the unit enclosing where it is written (static parent) → ... DYNAMIC scope = look at the call chain: local → my caller → its caller ... assignment to a nonlocal: changes the variable you found, visible after returning to the caller Java/C#: an inner block cannot redeclare the same local name; C/C++ hides it JS var = function scope, let = block scope