When learning C language from undergraduate time, we were taught:
++ i is incremented first and then assigned
i ++ is assigned first and then incremented
Recently I am looking at concurrent programming of jvm virtual machine and java. I hope to introduce it from the perspective of bytecode.
In fact, many people have written this detail, but I still want to make a little synthesis and describe it in a more popular and intuitive way.
To read this article, you need to have a certain understanding of the Java Virtual Machine (Java Virtual Machine, JVM), at least you need to understand the meaning and method of bytecode instructions (bytecode instructions) in the JVM virtual machine stack (JVM Stack) execution process . For details, please refer to "Understanding Java Virtual Machine: Advanced Features and Best Practices of JVM".
After reading the following, you will understand (rather than memorize) the running results of the following code:
The code snippet is taken from the difference between i ++ and ++ i in java
int a = 0;
for(int i = 0; i < 99; i++){
a = a++;
}
System.out.println(a); // 输出0
int b = 0;
for (int i = 0; i < 99; i++) {
b = ++ b;
}
System.out.println(b); // 输出99
int a = 0;
int b = 0;
for (int i = 0; i < 99; i++) {
a = a++;
b = a++;
}
System.out.println(a); // 输出99
System.out.println(b); // 输出98
Bytecode and virtual machine
The bytecode instruction in java is equivalent to the machine code in c / c ++.
The developer writes the source file (* .java) and compiles the source file into a class file understandable by the JVM through the javac command, which contains the constants, interfaces, fields, methods and other information of the class.
For each method, it contains line number table (LineNumber Table), local variable table (LocalVariable Table), byte code, exception table (Exception Table) and other information.
Use the javap
commands under the JDK or jclasslib bytecode viewer software to view the bytecode information.
Each method is composed of several bytecode instructions, which will affect the local variables (Local Variables) and operand stack (Operand Stack) of the current method . Note that only one local variable table and operand stack are maintained in a method body; the operand stack holds the value of the current variable, and the elements in it are only performed through two operations, push and pop.
For the specific instruction meaning, please refer to the instructions in the official JVM specification of Oracle Chapter 6. The Java Virtual Machine Instruction Set
Command name | Changes in the operand stack | description |
---|---|---|
iconst_<i> |
i Push |
Put an integer on the i stack |
istore_<i> |
Top element pops (pop) | Pop the integer at the top of the stack and assign it to i the element with index in the local variable table |
iload_<n> |
Push the middle element of the local variable table | Put n the element with index in the local variable table on the stack |
iinc i by n |
> No change | i Add to the element with index in the local variable tablen |
iadd |
Pop two elements at the top of the stack and return one element (pop, pop, push) | Pop two elements consecutively from the top of the stack, and put the result of adding them back onto the stack |
getstatic<i> |
Put symbol references on the i stack |
Put symbol references on the i stack |
invokevirtual<n> |
..., objectiveref, [arg1, [arg2...]] -> ... (pop, pop, pop ,...) | Call method n of objectref in the stack, and use arg1, arg2 as the parameters of the method |
It is important to note here that the iinc
auto-increment instruction directly accumulates the elements of the local variable table instead of on the stack.
If readers who don't understand the instruction set may still be in the fog, it doesn't matter, the following will introduce it with a graphical method.
Separate i ++ and ++ i operations
If there is only i++
or ++i
, such as:
int i = 0;
i++;
int i = 0;
++i;
The bytecode instructions are the same, both are:
0 iconst_0
1 istore_1
2 iinc 1 by 1
5 return
The figure describes the bytecode execution mechanism of the above code: line 0 adds 0 to the top of the operand stack; line 1 stores the element at the top of the stack to the index 1 of the local variable table, that is i
; Behavior increment operation. Note that there is no stack operation here. In the local variable table, directly add the variable with index 1 to 1; the fifth behavior is the return statement, and there is no return value.
Through the above introduction, the reader may probably understand the execution principle of bytecode. Note that the bytecode is the same whether it is i++
or ++i
not. If we write for(int i = 0; i<len; i++/++i)
can
i ++ and ++ i assignment
Let's take a look at i++
the case of assignment:
int i = 0;
i = i++;
System.out.println(i); // i = 0
How do you understand that when you assign a value, you i
still get 0? The byte code is as follows (excluding print output statements):
0 iconst_0
1 istore_1
2 iload_1
3 iinc 1 by 1
6 istore_1
7 return
It can be seen from the bytecode instruction that when it i++;
becomes i=i++;
, line 2 load
first i
copies the value to the top of the stack through the command , and then i
increments on line 3 , but at this time the increment is executed in the local variable table 0->1
, However, the value on the stack remains unchanged; then on line 6, the value at the top of the stack is again assigned to index 1 in the local variable table, that is, to the variable i
, so it i
is still 0.
Such results are still i++
assigned first, and then increase without conflict. i
I first copied a value on the top of the stack and incremented it, but then I assigned the top of the stack to the left end item i
just to be myself.
Next, look at the ++i
assignment:
int i = 0;
i = ++i;
System.out.println(i); // i = 1
The byte code is as follows (excluding print output statements):
0 iconst_0
1 istore_1
2 iinc 1 by 1
5 iload_1
6 istore_1
7 return
It can be seen from the bytecode that in i=++i
the second i
row after the increment, the local variable table becomes 1, then the fifth line loads i from the variable table, and the sixth line is assigned back. At the same time, i=++i;
this line of statement will also prompt the message "The assignment to variable i has no effect", meaning that such an assignment is meaningless. As explained by the bytecode, we load i
the value and assign it back.
From the above comparison, we can see that in the case of assignment:
i++
First load the original value into the stack, increment the variable table, and then assign the value in the stack to the corresponding variable;
++i
First increase the variable table, load it into the stack, and then assign the value in the stack to the corresponding variable.
Examples of other situations
Example 1 (from the interpretation of i ++ and ++ i with java bytecode ):
int i = 0;
int j = 0;
j = i++ + i++;
System.out.println(j); //输出j=1
System.out.println(i); //输出i=2
Why j = 1, i = 2? The bytecode is interpreted as:
0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iload_1
5 iinc 1 by 1
8 iload_1
9 iinc 1 by 1
12 iadd
13 istore_2
14 return
Seen from bytecode, 0-3 initialization behavior i
and j
values of each calculated i++
, the first is i
the old value is loaded into the stack and then i
increment. At the first time , the local variable table i++
in the stack i=0
is i=1
; at the second i++
time, the stack is loaded after the increment i
, namely 1, and then i
incremented again in the variable table at this time i=2
. The last i++ + i++
value to be calculated , so the iadd
instruction will pop up the two elements in the stack and add 1 to it, and then return it to the top of the value stack. Finally, the istore
instruction assigns the value to the j
operation and the operation ends. Therefore, we finally got the i=2,j=0+1=1
result.
Example 2:
int i = 0;
int j = 0;
j = i++ + i++ + i++;
The bytecode file is as follows:
0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iload_1
5 iinc 1 by 1
8 iload_1
9 iinc 1 by 1
12 iadd
13 iload_1
14 iinc 1 by 1
17 iadd
18 istore_2
19 return
The result of this operation is i=3,j=3
.
According to the previous case, it can be deduced: i ++ has been calculated 3 times, therefore i=3
;j=0 + 1 + 2 =3
Example 3: Viewing i ++ and ++ i from the perspective of JVM
int i = 0;
i = i++ + ++i; //i最终为2
The bytecode is:
0 iconst_0
1 istore_1
2 iload_1
3 iinc 1 by 1
6 iinc 1 by 1
9 iload_1
10 iadd
11 istore_1
12 return
I will not write the specific execution process, I believe everyone can understand it by looking at the picture.
summary
I have skipped the example at the beginning, and if you push it carefully, you can certainly introduce it.
Before I discuss i++
or ++i
some other questions like the same boring "fennel" several writing, but the look on until the Java virtual machine and concurrency only after really understand why i++
the operation does not have the atomicity, it is because in the instruction code It is not an instruction, but has three operations of reading, adding and assigning.