Variables
Table of Contents
Introduction
Think back to your first time encountering an algebraic formula; say something as simple as y = x + 1. You likely learned that x is an unknown value, or at least a value which isn't known the moment you glanced at the formula. x is a stand-in, a signifier, a symbol, a name, i.e., something that represents the presence of some magnitude. x is a variable. The value attached to x is subject to change. The potential value attached to it is external to the formula itself.
Variables in Programming Languages
Variables within programming languages satisfy a similar, although not identical, position to variables within algebraic formulas1. Variables are names for data stored in the computer. When we write x = 1 we are saying that x is a name which represents the computational object 1 which exists somewhere within the hardware of the machine running the program. As the name suggests, variables can change values. For example, we can write x = x + 1 which increments the values of x.
Scope
All identifiers, including variable names, will have a scope associated with them. Scope has two facets in C: (1) the lexical scope; (2) the linkage scope. Here, we focus on the lexical scope which refers to "the region of the program text within which the identifier's characteristics are understood"2. An example should be instructive:
1: public class Scope { 2: // Block 0, "Global" scope 3: static int g = 4; 4: 5: public static void main(String[] args) { 6: // Block 1, Outer main method 7: int x = 1; 8: { 9: // Block 2, Inner main method 10: int y = 2; 11: { 12: // Block 3, Innermost main method 13: int z = 3; 14: } 15: } 16: } 17: }
Listing 4 shows a Java program which defines 4 int type variables: g, x, y, z, each one within a different code block. In Java, braces, "{}", indicate the starting and closing of a code block, i.e., a collection of statements (you can think of statements as the sentences of programming languages). These blocks are the arbiter's of a variable's lexical scope (hereon referred to simply as "scope"). When a variable is defined in a given block, it can be "seen" by all statements/expressions within the same block as well as interior blocks. For example, g is visible at Block levels 0, 1, 2, and 3, despite it only being declared in Block 0. We call Block 0 the global scope—this refers to anything declared outside of a function block (such as the function main). Furthermore, x is visible at Block levels 1, 2, and 3, but not at Block 0. Every time a block is exited, all of the names defined therein are forgotten. To further emphasize this I will update Listing 4 so that z is referenced in Block 1 after it has been defined in Block 3:
1: public class Scope { 2: // Block 0, Global scope 3: static int g = 4; 4: 5: public static void main(String[] args) { 6: // Block 1, Outer main method 7: int x = 1; 8: { 9: // Block 2, Inner main method 10: int y = 2; 11: { 12: // Block 3, Innermost main method 13: int z = 3; 14: } 15: } 16: x = z + 1; 17: } 18: }
When I try to compile this code, I get the following error:
josephraskind@stargazer:/tmp/scope$ javac Scope.java
Scope.java:16: error: cannot find symbol
x = z + 1;
^
symbol: variable z
location: class Scope
1 error
Why does the compiler say that z cannot be found? I clearly define it in Block 3, no?
Again, this has to do with the way scope works in Java. The moment we exit a block, we lose the ability to reach all of the identifiers defined within that block. This means z effectively vanishes from the perspective of the compiler the moment we exit Block 3 (Line 12).
Scope rules also allows us to reuse old identifiers so long as we do not use the same identifier in the same scope. For example, take a look at the following code which defines x twice in main:
1: public class Dec{ 2: public static void main(String[] args){ 3: int x = 1; //Declaring x 4: int x = 2; //Declaring x, again 5: } 6: }
If I try to compile that code:
josephraskind@stargazer:/tmp/scope$ javac Dec.java
Dec.java:4: error: variable x is already defined in method main(String[])
int x = 2; //Declaring x, again
^
1 error
I get an error that tells me I "already defined" x. This happens because all identifiers within a given scope must be unique. I cannot have two different memory locations tied to the same name, there must be a one-to-one mapping within a scope between name and memory location at all times. If I wanted to fix the above error, I would need to define each x in their own, mutually exclusive scopes (I will leave that as an exercise for the reader).
Scope will become relevant again when we discuss functions!
What a Variable Really Is in Java
In Java, variables are not purely symbolic names. They are intimately tied up with the memory semantics of the JVM runtime system. They are truly just standins for locations in memory3.
To illustrate this I will write a simple Java program which declares two int variables and adds them together and stores the result in a third variable:
1: public class Adder{ 2: public static void main(String[] args){ 3: int a = 100; 4: int b = 200; 5: int c = a + b; 6: 7: } 8: }
Then I will compile the program down to its JVM bytecode representation with javac Adder.c; javap -c Adder.class. We can see the resultant JVM bytecode file here:
1: Compiled from "Adder.java" 2: public class Adder { 3: public Adder(); 4: Code: 5: 0: aload_0 6: 1: invokespecial #1 // Method java/lang/Object."<init>":()V 7: 4: return 8: 9: public static void main(java.lang.String[]); 10: Code: 11: 0: bipush 100 12: 2: istore_1 13: 3: sipush 200 14: 6: istore_2 15: 7: iload_1 16: 8: iload_2 17: 9: iadd 18: 10: istore_3 19: 11: return 20: }
The main method's Code label shows where our program's code begins. We can see our constants clearly on Lines 11 and 13 which must mean that istore_1 and istore_2 are directly related to a and b, respectively. How can that be? The constants are stored inside of the JVM's local variable array. It is nothing more than a location in memory which is managed by the JVM. By the time we reach JVM runtime land, we no longer have symbolic names like a and b, but rather purely locations in memory managed by the JVM runtime which is outlined by the compiler.
Exercises
- Look at Listing 4 again, and edit the code such that x is defined twice in
main, but the compiler does not throw any errors. - Compile Listing 6 with
javac Adder.javaand inspect the bytecode withjavap -c Adder.class. Identify which lines in the bytecode correspond to each of the three variable declarations. What local variable slots are used for a, b, and c? Why do you think slot 0 is skipped? - Modify Listing 6 to add a fourth variable
int d = c * 2and recompile. Runjavap -cagain. What new bytecode instructions appear? Which slot is d assigned to? - Write a Java program that declares a static field
static int g = 10outsidemainand a local variableint g = 20insidemain. Does it compile? Which value gets printed and why? Now try to print the staticgfrom inside the block that defines the localgusingClassname.g—whereClassnameis replaced with the name of your class—is it possible? - The lecture states that once a block is exited its variables "vanish from the perspective of the compiler." Write a Java program that demonstrates this by attempting to access a variable from an inner block after that block has closed. Record the exact compiler error you receive and explain in your own words what it means in terms of scope.
- Earlier in the lecture, I wrote the code snippet
x = x + 1and referred to it as "incrementing" x. Outside of computer science,x = x + 1likely looks like a nonsense statement. From what you now know about variables, how is that possible in a programming language? Write a Java program that defines an integer x, assigns it the value 100, and increments it by 200. Then inspect the bytecode withjavap -cto see how the JVM represents this operation.