从字节码讲解i++和++i的区别|8月更文挑战

简介: 从字节码讲解i++和++i的区别,i++真的是先赋值再运算吗?让我们从底层一探究竟,彻底弄明白i++和++i的运算过程。

从字节码讲解i++和++i的区别

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

jvm栈

我们知道每一个线程有一个私有的虚拟机栈,在虚拟机栈中每个调用的方法又有一个栈帧,即方法的调用就是栈帧的入栈和出栈。

栈帧是一个内存区块,维系着方法执行的各种数据信息。不同的线程的栈帧不允许相互引用。

栈帧存储着局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息。一些地方将动态链接、方法返回地址、一些附加信息并称为帧数据区。

  • 局部变量表:存储方法的参数、方法内的局部变量(基本数据类型和引用数据类型以及返回值类型的数据),32位的占一个变量槽,64位占两个变量槽。
  • 操作数栈:根据字节码指令,往栈中写入数据或提取数据。
  • 动态链接:指向运行时常量池,因为编译的时候,变量和方法都作为符号引用保存在常量池中,方法之间的调用就是通过这些符号引用来表示,而动态链接就是将这些符号引用,变成直接引用。
  • 方法返回地址:存放的调用该方法的PC寄存器的值,用户方法执行完之后回到被调用的位置。正常退出返回调用者的PC寄存器的值,即调用该方法的指令的下一条指令的地址。异常退出则根据异常表来确定返回的地址,栈帧不保存这部分信息。
  • 一些附加信息:java虚拟机的一些信息。

IDEA中查看类字节码

一、Idea Tools

使用jdk命令结合Idea Tools来查看。

添加工具

在File -> Settings -> Tools ->External Tools点击+号
image-20210730181319409.png

Name:随意填写
Program:$JDKPath$\bin\javap.exe
Arguments:-v -c $FileNameWithoutExtension$.class
Working directory:$OutputPath$$FileDirRelativeToSourcepath$

使用

当你打开java文件后,点击Tools->External Tools ->上述的Name值,即可看见字节码。

二、插件

安装插件

File ->Settings->Plugins搜索插件:jclasslib Bytecode Viewer 并安装

使用

使用之前先编译java文件,编译完之后,在右侧会有Jclasslib点开即可,如果没有,View->Show Bytecode With Jclasslib.

i++和++i的区别

示例一: i++

先通过这个简单的小例子入手,相信这个例子大家大会做。

package com.wangscaler.Increment;
​
/**
 * @author WangScaler
 * @date 2021/7/30 18:00
 */
​
public class Main {
    public static void main(String[] args) {
        int i = 0;
        System.out.println(i++);
        System.out.println(i);
    }
}

打印的结果是0、1。我们通过字节码来看执行过程。

 0 iconst_0
 1 istore_1
 2 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
 5 iload_1
 6 iinc 1 by 1
 9 invokevirtual #3 <java/io/PrintStream.println : (I)V>
12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
15 iload_1
16 invokevirtual #3 <java/io/PrintStream.println : (I)V>
19 return
  • i代表int,const代表常量,iconst_0就是将常量0入栈。
  • store代表存储,istore_1就是将栈顶的值出栈,放到局部变量1的位置。

    前两步示意图。

image-20210730184300279.png

  • 第三步动态链接运行时常量池#2,这句跟System.out.println()有关,这不过多介绍。
  • load就是加载变量的值,iload_1就是加载局部变量位置1的值到操作栈栈顶。

image-20210730192411568.png

  • inc就是自增操作,iinc 1 by 1 第一个1的意思是局部变量表位置1,所以整句话的意思就是在局部变量表1的值的基础上自增1。

image-20210730192423596.png

  • 调用打印的方法打印,打印的是栈中的值0
  • iload_1,同上将局部变量位置1的值(此时是1)入栈到栈顶
  • 打印1

示例二: ++i

在上述代码的基础上追加代码

System.out.println(++i);
System.out.println(i);

在实例一中i的值是1,所以现在的打印结果是2、2。还是通过字节码一探究竟。

19 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
22 iinc 1 by 1
25 iload_1
26 invokevirtual #3 <java/io/PrintStream.println : (I)V>
29 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
32 iload_1
33 invokevirtual #3 <java/io/PrintStream.println : (I)V>
36 return
  • 从第二句开始,iinc 1 by 1,局部变量表位置1的值自增1。所以现在局部变量表位置1的值为2
  • iload_1。将局部变量表1的值(2)入栈栈顶。所以操作数栈栈顶的值为2。
  • 调用打印,打印栈顶的值2。
  • 下面是重复操作,又将局部变量表的值重新入栈,但是期间值没有变化。

示例三: i= i++

继续追加代码

i = i++;
System.out.println(i);

此时i的初始值是2。打印的结果还是2。字节码如下:

6 iload_1
37 iinc 1 by 1
40 istore_1
41 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
44 iload_1
45 invokevirtual #3 <java/io/PrintStream.println : (I)V>
48 return
  • iload_1。再次将局部变量表1的值(2)入栈栈顶。所以现在局部变量表位置1的值为2。
  • iinc 1 by 1。局部变量表1的值自增1,当前值为3。
  • istore_1。将栈顶的值出栈,放入局部变量表1的位置。所以局部变量表的值又变成了2。
  • iload_1。将局部变量的值2入栈栈顶。
  • 调用打印,打印的值为2

从这可以看出,之前说的i++是先赋值再自增,其实是不对的,i++依然是先自增。但是对于值的计算 ,这个说法还是可以接受的。

示例四: i= ++i

继续追加代码

i = ++i;
System.out.println(i);

此时i的初始值是2,打印的结果是3

48 iinc 1 by 1
51 iload_1
52 istore_1
53 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
56 iload_1
57 invokevirtual #3 <java/io/PrintStream.println : (I)V>
60 return
  • iinc 1 by 1。局部变量表1的值自增1,自增之后的值是3。
  • iload_1。局部变量表1的值3入栈,当前栈顶的值3。
  • istore_1。栈顶元素出栈,放入局部变量1的位置。
  • 入栈栈顶元素值3
  • 调用打印,所以打印的值为3

示例五: int j= i++

追加代码如下:

int j = i++;
System.out.println(j);
System.out.println(i);

此时i的初始值为3。打印的结果你能算出来了吗?没错就是3、4。

字节码如下:

60 iload_1
61 iinc 1 by 1
64 istore_2
65 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
68 iload_2
69 invokevirtual #3 <java/io/PrintStream.println : (I)V>
72 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
75 iload_1
76 invokevirtual #3 <java/io/PrintStream.println : (I)V>
79 return
  • 局部变量表1的值3入栈,当前栈顶的值3。
  • iinc 1 by 1。局部变量表1的值自增1,当前值4。
  • istore_2。栈顶元素出栈,放入到局部变量表2的位置,注意不是位置1
  • iload_2。局部变量表2的值3入栈栈顶。
  • 调用打印语句,所以打印的是3。
  • iload_1。局部变量表1的值4入栈,当前栈顶的值4。
  • 调用打印语句,所以打印的是4。

示例六: j= ++i

追加代码如下,i的初始值4

j = ++i;
System.out.println(j);
System.out.println(i);

字节码如下:

79 iinc 1 by 1
82 iload_1
83 istore_2
84 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
87 iload_2
88 invokevirtual #3 <java/io/PrintStream.println : (I)V>
91 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
94 iload_1
95 invokevirtual #3 <java/io/PrintStream.println : (I)V>
98 return

示例七: i + i++ + ++i

继续追加代码。i的初始值是 5,

System.out.println(i + i++ + ++i);

字节码

 98 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
101 iload_1
102 iload_1
103 iinc 1 by 1
106 iadd
107 iinc 1 by 1
110 iload_1
111 iadd
112 invokevirtual #3 <java/io/PrintStream.println : (I)V>
115 return

牛刀一试

最后两个示例,我没有写结果,便于让你学以致用,自己去试试看看是否真的听懂了。

目录
相关文章
|
2月前
|
监控 Oracle Java
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
40 0
|
2月前
|
存储 缓存 算法
高效编程:我们应该了解哪些编译器优化技术?如何做出成熟的优化行为,掌握C++编程中的编译器优化艺术。
高效编程:我们应该了解哪些编译器优化技术?如何做出成熟的优化行为,掌握C++编程中的编译器优化艺术。
101 4
|
2月前
|
存储 Java 索引
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
34 0
|
3月前
|
编译器 C语言 Windows
【2月更文挑战第1篇】C程序结构
C 程序结构 在我们学习 C 语言的基本构建块之前,让我们先来看看一个最小的 C 程序结构,在接下来的章节中可以以此作为参考。 C Hello World 实例 C 程序主要包括以下部分: 预处理器指令 函数 变量 语句 & 表达式 注释 让我们看一段简单的代码,可以输出单词 "Hello World": 实例: ```c #include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n"); return 0; } ``` 接下来我们讲解一下上面这段程序: 程序的第一
20 1
|
11月前
|
C语言
可能你看到的大部分教材里讲的指针和指针变量是一个概念,但是真的是这样吗?看完我这篇文章肯定会颠覆你的认知哦?
可能你看到的大部分教材里讲的指针和指针变量是一个概念,但是真的是这样吗?看完我这篇文章肯定会颠覆你的认知哦?
112 0
可能你看到的大部分教材里讲的指针和指针变量是一个概念,但是真的是这样吗?看完我这篇文章肯定会颠覆你的认知哦?
|
存储 自然语言处理 前端开发
夯实基础,编译器原理前端部分浅析
如果说计算机网络、操作系统、数据结构这些是编程必学基础,我能理解,现在连编译器原理都是必备基础了吗?是的,我们太习惯于从高级语言学起了,反而忘了C、C++、Java 这些高级语言是如何一层一层解析直至被计算机读懂的。正本清源,我们对编译器的认知,应该提到和操作系统、数据库、浏览器、编程语言、算法这些编程基础技能同一水平。
|
SQL 缓存 Java
阿里面试官让我讲讲volatile,我直接从HotSpot开始讲起,一套组合拳拿下面试
阿里面试官让我讲讲volatile,我直接从HotSpot开始讲起,一套组合拳拿下面试
阿里面试官让我讲讲volatile,我直接从HotSpot开始讲起,一套组合拳拿下面试
|
算法 Oracle Java
深度剖析 | 【JVM深层系列】[HotSpotVM研究系列] JVM调优的"标准参数"的各种陷阱和坑点分析(攻克盲点及混淆点)「 1 」
深度剖析 | 【JVM深层系列】[HotSpotVM研究系列] JVM调优的"标准参数"的各种陷阱和坑点分析(攻克盲点及混淆点)「 1 」
111 0
|
存储 Java 关系型数据库
【底层原理之旅—攻克你的技术盲点之JVM常量池】|Java 刷题打卡
【底层原理之旅—攻克你的技术盲点之JVM常量池】|Java 刷题打卡
118 0
【底层原理之旅—攻克你的技术盲点之JVM常量池】|Java 刷题打卡
|
存储 监控 算法
JVM技术之旅-进阶分析内存布局特性
JVM技术之旅-进阶分析内存布局特性
105 0
JVM技术之旅-进阶分析内存布局特性