Every method has a pool of up to 65,536 local variables. Very few methods use this many; most use fewer than 100. The actual number is given by the .limit locals directive immediately after the .method directive:
.method public foo ()V .limit locals 5 ; No more than 5 locals may be used
If you omit the .limit locals directive, the Oolong assembler will guess that it should reserve enough space for the highest-numbered local variable in your method. You can use the .limit locals directive if you think you know better than the Oolong assembler.
Local variables are similar to, but not quite the same as, the local variables in Java programs. Oolong local variables don't have names. Variables are referenced by number, starting at 0. As with stack entries, long and double values take up two consecutive local variables. You refer to it by the lower number. You can have a long in variable 1, in which case you may not use variable 2 because it's already spoken for by the long in 1.
There's no requirement for aligning long and double values. You can have a double value in variables 1 and 2 and another one in 4 and 5.
The instructions that deal with local variables are called load and store instructions. These load values from the local variable onto the stack and store values from the top of the stack into a local variable. "Load" and "store" are familiar to most programmers as "push" and "pop." They are used like this:
aload 0 ; Push the reference in local 0 onto the stack iload 5 ; Push the int in local 5 lstore 3 ; Store the long on top of the stack ; into locals 3 and 4
For efficiency reasons, there are special instructions to load and store local variables 0, 1, 2, and 3. You might imagine that local variable 0 is used much more frequently than local variable 245. For example,
has exactly the same effect as
except that the former requires slightly less space in the resulting class file. Depending on the JVM implementation you use, it might even be a little more efficient.
Unlike Java, variables are dynamically typed, not statically typed. You can store any of the five stack types (int, float, double, long, reference) in any variable. This means that you can use slot 3 to store an int early in the method, then use it again later to store a float or half a double. The verification algorithm even approves of this behavior.
A local variable can hold values of different types at different times. The following example stores a String, an int, and a double in the same local variable, one after the other:
ldc "A String" ; Put a string onto the stack astore_0 ; Store a string into variable 0 sipush 12345 ; Put an int onto the stack istore_0 ; Store an int into the same variable ldc 2.718281828D ; Put a double on the stack dstore_0 ; Store the double into variables 0 and 1
When you load from a local variable, you must use the load instruction that corresponds to the type of the value that is stored in the field. This code is illegal:
iconst_3 ; Load the constant 3 onto the stack istore_2 ; Store it in variable 2 aload_2 ; FOUL! Must use an iload instead
Also, you must use a store instruction appropriate to the value on top of the stack.
ldc "Hello" ; Push a reference onto the stack fstore 3 ; FOUL! You must use an astore instead
The JVM verification algorithm checks for illegal operations before the code is permitted to run. It errs on the side of caution: if it can't prove that the right instruction is used in every case, it rejects the class. The verification algorithm is discussed in detail in chapter 6.
Another thing to know about local variables is that double and long values take up two local variables, not one. For example,
ldc "Hello, world" ; Push a String astore_2 ; Store it in variable 2 ldc 3.14159D ; Push a double-precision value dstore_1 ; Store it in both variables 1 and 2
The former contents of variable 2 are wiped out, so if you now try
aload_2 ; FOUL!
the JVM will find that variable 2 no longer holds a reference and the verification algorithm will reject the program.
Tables 3.4 and 3.5 summarize the load and store instructions.
3.7.1 Initializing Variables
All variables must be explicitly initialized before they are used. JVM fields are initialized to default values (0 for numeric types, null for reference types). Local variables are not initialized. Programs must store into a local variable before reading from it. Otherwise, the program will be rejected by the verification algorithm.
Initialization means that you have to do a store before you do any loading. To initialize variable 7 to an int 3, use these instructions:
.method static public someMethod()V ; All variables are currently uninitialized iconst_3 istore 7 ; Variable 7 is now initialized to 3 ;; The rest of the method .end method
This code is illegal:
.method static public someIllegalMethod()V iload_0 ; FOUL! Variable 0 is uninitialized ;; The rest of the method doesn't matter .end method
The JVM verification algorithm is capable of detecting whether or not all variables are initialized before allowing your code to run. If your code is running, you can be sure that there are no variables used before initialization. This feat is actually rather remarkable, since in general it's impossible to predict how a program will run just from looking at it. Read chapter 6 to find out how its done.
3.7.2 Local Variables as Method Arguments
Some local variables are initialized for you automatically by the JVM. These local variables are used as method arguments. For example, consider this method:
.class DemoClass .method static lotsOfArguments(IJF[[Ljava/lang/String;)D
This corresponds to the Java method declaration
static double lotsOfArguments (int a, long b, float c, String d)
Local variable 0 is assigned to the first argument, local variable 1 to the second, and so on. In this case, five local variable slots are required, corresponding to the Java variables a, b, c, and d. Five slots are required for four variables because variable b requires two slots because it is a long.
If you call the method like this (from Java),
DemoClass.lotsOfArguments(9, 1066L, 99.44, new String)
then variable 0 contains the int 9, variables 1 and 2 have the long 1066, variable 3 has the float 99.44, and variable 4 contains a reference to an array of Strings. If the method is not static, there is an additional argument. That argument is variable 0, and it corresponds to this in Java. For example,
.class MyClass .method doSomeMath(IFF)F
Four local variable slots are initialized in this method. Local variable 1 is an int, and variables 2 and 3 are floats. Variable 0 is a reference to an instance of the class MyClass, and it's guaranteed not to be null. Suppose you invoke the method like this (from Java):
MyClass obj = new MyClass(); float return = obj.doSomeMath(100, 14.0, 3.14);
In the body of the method, variable 0 will be a reference to obj. Variable 1 will be 100, 2 will be 14.0, and 3 will be 3.14. Any other variables will be uninitialized.
The return of the method is not allocated a particular variable. Even though the method returns a float, there is no variable corresponding to that float. Unlike the arguments, the return value is not assigned a slot. Instead, the returned value comes from the operand stack.