Answers to Self-Study Questions

Test Yourself #1

Question 1:

class animal {  // OK declaration of animal as a class
  // methods
  void attack(int animal) { OK declaration of attack as a method and animal as a param/local
     for (int animal=0; animal<10; animal++) {  // ERROR: animal already declared as a param/local
         int attack; // OK declaration of attack as a param/local
     }
  }

  int attack(int x) {  // ERROR: attack already declared as a method with same param OK declaration of x as a param/local
     for (int attack=0; attack<10; attack++) { // OK declaration of attack as a param/local
        int animal;  // OK declaration of animal as a param/local
     }
  }

  void animal() { }  // OK declaration of animal as a method

  // fields
  double attack;  // OK declaration of attack as a field
  int attack;  // ERROR: attack already declared as a field
  int animal;  // OK declaration of animal as a field
}

Question 2: In the following, each declaration is numbered, and each use is annotated with the number of the corresponding declaration (or is noted as an error).

int (1)k=10, (2)x=20;

void (3)foo(int (4)k) {
    int (5)a = x(2);
    int (6)x = k(4);
    int (7)b = x(6);
    while (...) {
       int  (8)x;
	if (x(8) == k(4)) {
	   int (9)k, (10)y;
	   k(9) = y(10) = x(8);
	}
	if (x(8) == k(4)) {
	   int (11)x = y(ERROR);
	}
    }
}

Test Yourself #2

The output is: 10 0 10 20

Test Yourself #3

Question 1:

When we exit a nested scope (e.g., the body of an if or a loop), the declarations made in that scope are no longer valid (any future use of one of the names declared there is a use of an undeclared variable). Keep a separate symbol table for each scope means that we can still just add a new table on scope entry and remove the first symbol table in the list on scope exit. If we kept all declarations in a single symbol table, then on scope exit we'd have to look through the whole table to find and remove the symbols that were declared in the scope being exited.

Question 2:

Under the scoping rules we've been assuming:

+--------------+    +-------------------------------+
| x: int, 2    |--->| g: (int, int) -> void, 1      |
| y: int, 2    |    | f: (int, int, int) -> void, 1 |
| z: int, 2    |    +-------------------------------+
| a: int, 2    |
| b: int, 2    |
+--------------+    

Under actual C++ scoping rules (where parameters are in a different scope than local variables):

+-----------+    +--------------+    +-------------------------------+
| a: int, 3 |--->| x: int, 2    |--->| g: (int, int) -> void, 1      |
| b: int, 3 |    | y: int, 2    |    | f: (int, int, int) -> void, 1 |
| x: int, 3 |    | z: int, 2    |    +-------------------------------+
+-----------+    +--------------+    

Question 3:

  1. Scope entry: No change
  2. Process a declaration: This would change. A lookup would be done in all enclosing scopes (i.e., in all hashtables currently in the list). If the name being declared is in any table and has the same "kind" as the current declaration, it would be an error.
  3. Process a use: This would change. A lookup would be done in each hashtable in the list (starting from the one for the current scope). If the name is found, its list of kinds would be examined to see if it includes the expected kind. If yes, we're done! Otherwise, the loopup would contine in the remaining hashtables in the list. If no match is found, this would be an error (a use of an undeclared name).
  4. Scope exit: No change.

Test Yourself #4

After processing the header for function g:

+-----------------------------------------+
|         +--------------------+          |
|  g:     | int,int -> void, 1 |          |
|         +--------------------+          |
+-----------------------------------------+

After processing the top-level local declaration in g:

+-----------------------------------------+
|         +--------------------+          |
|  g:     | int,int -> void, 1 |          |
|         +--------------------+          |
|                                         |
|         +-----------+                   |
|  d:     | double, 2 |                   |
|         +-----------+                   |
+-----------------------------------------+

After processing the declarations in g's first while loop:

+-----------------------------------------+
|         +--------------------+          |
|  g:     | int,int -> void, 1 |          |
|         +--------------------+          |
|                                         |
|         +--------+    +-----------+     |
|  d:     | int, 3 |--->| double, 2 |     |
|         +--------+    +-----------+     |
|                                         |
|         +--------+                      |
|  w:     | int, 3 |                      |
|         +--------+                      |
|                                         |
|         +-----------+                   |
|  x:     | double, 3 |                   |
|         +-----------+                   |
|                                         |
|         +-----------+                   |
|  b:     | double, 3 |                   |
|         +-----------+                   |
|                                         |
+-----------------------------------------+

After processing the declarations in g's if statement:

+-----------------------------------------+
|         +--------------------+          |
|  g:     | int,int -> void, 1 |          |
|         +--------------------+          |
|                                         |
|         +--------+    +-----------+     |
|  d:     | int, 3 |--->| double, 2 |     |
|         +--------+    +-----------+     |
|                                         |
|         +--------+                      |
|  w:     | int, 3 |                      |
|         +--------+                      |
|                                         |
|         +-----------+                   |
|  x:     | double, 3 |                   |
|         +-----------+                   |
|                                         |
|         +--------+    +-----------+     |
|  b:     | int, 4 |--->| double, 3 |     |
|         +--------+    +-----------+     |
|                                         |
|         +--------+                      |
|  a:     | int, 4 |                      |
|         +--------+                      |
|                                         |
|         +--------+                      |
|  c:     | int, 4 |                      |
|         +--------+                      |
+-----------------------------------------+

After exiting the scope of g's if statement, we're back to the same table as the one shown above for "After processing the declarations in g's first while loop", and after exiting the scope of g's first while loop we're back to the same table as the one shown above for "After processing the top-level local declaration in g".

After processing the declarations in g's second while loop:

+-----------------------------------------+
|         +--------------------+          |
|  g:     | int,int -> void, 1 |          |
|         +--------------------+          |
|                                         |
|         +-----------+                   |
|  d:     | double, 2 |                   |
|         +-----------+                   |
|                                         |
|         +--------+                      |
|  x:     | int, 3 |                      |
|         +--------+                      |
|                                         |
|         +--------+                      |
|  y:     | int, 3 |                      |
|         +--------+                      |
|                                         |
|         +--------+                      |
|  z:     | int, 3 |                      |
|         +--------+                      |
+-----------------------------------------+