欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java i++ 与 ++i 原理

程序员文章站 2022-06-05 13:48:03
...

本文目录

前言

1 语句层面

2 计算过程

3 分析原理

4 示例分析


 

前言

记得大学期间学习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)
}