本文目录
前言
记得大学期间学习C语言的时候,对于 i++ 和 ++i (i-- 和 --i)的理解,一直似懂非懂的状态,因为很多人给出的解释都不够严谨,比如:i++是在操作之后增加,而++i是在操作之前增加。但是,如何确定“操作”的时间点呢?由于平时使用的时候,没有出现过什么问题,为了代码的可读性,也不会刻意写一些“看似很牛X”的复杂且冗长的代码,这个问题也就没有去深究。今天突然心血来潮,想弄清楚其中的原理,并以此文作为记录。
1 语句层面
类似 i += 1 的语句,属于高级语言层面的语句(语法糖),因为,我们可以用另一种代码形式来解释该语句,
例如:i += 1 等同于 i = ( i + 1 )
而 i++ 和 ++i 语句,属于低级语言层面的语句,我们需要从反编译代码中解读其详细的运行机制。
2 计算过程
程序中的变量会存储在主内存中,当需要计算时,主内存中的变量值会被加载到相应的寄存器中,CPU计算过程中,会从寄存器中读取数据,并将计算结果写回寄存器,最终写回主内存。
/**
* 示例代码
*/
public class demo {
public void test() {
int a = 1, b = 2;
/*
* 首先,会将 a 和 b 的内存值依次加载至寄存器;
* 然后,CPU从寄存器中读取两个值并执行相加运算;
* 接着,将计算结果写回寄存器;
* 最终,写回 sum 主内存值。
*/
int sum = a + b;
}
}
3 分析原理
通过一个简单的示例程序,根据其反编译内容解读 i++ 和 ++i 的底层操作流程。
/**
* 示例代码
*/
public class demo {
public void test1() {
int i = 0;
int j = i++;
}
public void test2() {
int i = 0;
int j = ++i;
}
public void test3() {
int i = 0;
int j = i++ + i++;
}
}
通过 javac demo.java 进行编译,得到 demo.class 字节码文件。
再通过 javap -c demo.class 进行反编译,得到反编译代码如下:
public class demo {
public demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void test1();
Code:
0: iconst_0 // 入栈常数0
1: istore_1 // 赋值1号存储单元为常数0(i = 0)
2: iload_1 // 加载1号存储单元值到寄存器(记为stack[0] = 0)
3: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 0 + 1 = 1)
6: istore_2 // 赋值2号存储单元为寄存器值(j = stack[0] = 0)
7: return // 返回(此时:i = 1,j = 0)
public void test2();
Code:
0: iconst_0 // 入栈常数0
1: istore_1 // 赋值1号存储单元为常数0(i = 0)
2: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 0 + 1 = 1)
5: iload_1 // 加载1号存储单元值到寄存器(记为stack[0] = 1)
6: istore_2 // 赋值2号存储单元为寄存器值(j = stack[0] = 1)
7: return // 返回(此时:i = 1,j = 1)
public void test3();
Code:
0: iconst_0 // 入栈常数0
1: istore_1 // 赋值1号存储单元为常数0(i = 0)
2: iload_1 // 加载1号存储单元值到寄存器(记为stack[0] = 0)
3: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 0 + 1 = 1)
6: iload_1 // 加载1号存储单元值到寄存器(记为stack[1] = 1)
7: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 1 + 1 = 2)
10: iadd // 取寄存器值并执行相加操作(stack[0] = stack[0] + stack[1] = 0 + 1 = 1)
11: istore_2 // 赋值2号存储单元为寄存器值(j = stack[0] = 1)
12: return // 返回(此时:i = 2,j = 1)
}
通过上述反编译内容可知,i++ 和 ++i 操作是在加载 i 值至寄存器步骤的前后执行的,而赋值语句右侧的表达式计算时,是根据加载到寄存器的值进行计算的。
理解了“操作”的时间点,我们便可以理解更复杂的 i++ 和 ++i 相关语句。
但在实际工作中,还是不建议写一些可读性较差的代码(复杂且冗长),不便于后期维护。
4 示例分析
/**
* 示例代码
*/
public class demo {
public void test() {
int i = 0;
/*
* ++ -- 操作符本身就具有很高的优先级(仅次于括号),
* 因此,这里有无括号,结果是一样的,
* 可化简为:int j = 5 - ++i + ++i;
*/
int j = 5 - (++i) + (++i);
}
}
public class demo {
public demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void test();
Code:
0: iconst_0 // 入栈常数0
1: istore_1 // 赋值1号存储单元为常数0(i = 0)
2: iconst_5 // 入栈常数5
3: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 0 + 1 = 1)
6: iload_1 // 加载1号存储单元值到寄存器(记为stack[0] = 1)
7: isub // 取寄存器值并执行相减操作(stack[0] = 5 - stack[0] = 5 - 1 = 4)
8: iinc 1, 1 // 递增1号存储单元值(i = i + 1 = 1 + 1 = 2)
11: iload_1 // 加载1号存储单元值到寄存器(记为stack[1] = 2)
12: iadd // 取寄存器值并执行相加操作(stack[0] = stack[0] + stack[1] = 4 + 2 = 6)
13: istore_2 // 赋值2号存储单元为寄存器值(j = stack[0] = 6)
14: return // 返回(此时:i = 2,j = 6)
}