程序员如何玩转汇编指令?


作者 | 帅地
责编 | 郭芮
我想大部分都知道 i++ 和 ++i的区别,i++ 就是先拿i来使用,之后再自增加1,而++i则是先自增加1,在拿i来使用。例如对于下面这两个语句,我敢保证大部分人都会做:
int i = 1;
System.out.println(i++)
int i = 1;
System.out.println(++1)
答案分别为 1,2,对于这个答案我猜大多数人都能答出来。不过 i++ 和 ++i 这两个操作,在内部是如何实现的呢?
我们先来看另外一个问题:
public static void main(String[] args) {
int i = 1;
System.out.println(i+++i++);
System.out.println(i);
}
这个比刚才那个难了点,答案分别是3,3。假如你对这个答案的由来了如指掌,那么你大不可必往下看,假如你不大理解或者想从底层的汇编指令的来了解这个操作,那么你可以看看我的解释。
首先我们先来看看 i++ 的题,主要是为了后面好解释点。
int i = 1;
System.out.println(i++);
这两行代码的部分汇编指令如下,注意,我只列出了几个重点的汇编语句:
ICONST_1 //把常量 1 加载到栈顶
ISTORE 1 //把栈顶的元素弹出,并赋值给局部变量表中位置为“1”的变量,此时指变量i。这两句就相当于 int i = 1;
//接下来执行第二行代码
ILOAD 1 //把局部变量表中位置为“1”的变量加载到栈顶,即把i的值加载到栈顶
IINC 1 1 //直接把局部变量表中位置为“1”的变量加1,即把 i 加1。注意,这条指令并没有修改操作数栈就把 i 加1了。
INVOKEVIRTUAL java/io/PrintStream.println (I)V //把栈顶的元素打印出来,此时栈顶的元素是 1。所以打印的是 1
所以,此时打印的是1。
有些人可能没弄过汇编会有点懵逼,没事,我花个时间画个图来模拟(注:省略很多细节)。
刚开始时的局部变量表和操作数栈如图所示:

1、执行 ICONST_1,常量 1 进栈:

2、执行 ISTORE 1,栈顶元素出栈存到位置“1”:

3、执行 ILOAD 1,把位置“1”的变量值存到栈顶:

4、执行 IINC 1 1 ,直接把局部变量表中位置为“1”的变量加 1:

5、执行 INVOKEVIRTUAL java/io/PrintStream.println (I)V,把栈顶的元素打印出来,此时栈顶的元素是 1:

所以虽然i已经等于2了,但此时栈顶的元素却是i之前的值 1 ,所以打印的是1。
这下关于 i ++ 的懂了吧?
那我们来看看 ++ i 与 i ++ 的汇编指令有什么不同。
int i = 1;
System.out.println(++i);
对应的部分重点汇编指令如下:
//和上面i++差不多,不过IINC 1 1 和ILOAD 1这两句的顺序调换了。
ICONST_1
ISTORE 1
IINC 1 1 //直接把局部变量表中位置为“1”的变量加1
ILOAD 1 //把位置“1”的变量压到栈顶,此时栈顶的元素是 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V //所以打印的是2
再画下图演示一下:
1、执行了ICONST_1 和ISTORE 1这两句过后的局部变量和栈的情况如下:

2、执行 IINC 1 1,注意,执行这条指令,操作数栈不会发生变化:

3、执行 ILOAD 1,把位置“1”的变量值压入栈顶:

4、执行 INVOKEVIRTUAL java/io/PrintStream.println (I)V,把栈顶的元素打印出来,此时栈顶的元素是 2:

所以,对于 i++ 和 ++i的区别彻底懂了吧。
接下来我们来分析这个程序:
int i = 1;
System.out.println(i+++i++);
System.out.println(i);
这里先说一下,按照运算符号的优先顺序,i+++i++等价于 (i++) + (i++)。
对应的部分汇编指令如下:
//第一行
ICONST_1
ISTORE 1
//第二行
ILOAD 1
IINC 1 1
ILOAD 1
IINC 1 1
IADD //把栈顶的两个元素弹出相加之后在把结果放回栈顶
INVOKEVIRTUAL java/io/PrintStream.println (I)V
//第三行
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
如果上面的那两个 i++ 和 ++i 你看懂了,那么上面那个汇编应该也差不多能看懂。我用图来逐条分析一下吧。
1、执行了 ICONST_1 和ISTORE 1之后的状态如下:

2、执行 ILOAD 1:

3、执行 IINC 1 1:

4、执行 ILOAD 1:

5、执行 IINC 1 1:

此时实际上 i 的值已经是 3 了,只是栈顶放的都是 i 的旧值。
6、执行 IADD ,把栈顶两个元素出栈相加后再把结果入栈:

7、执行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此时栈顶元素为3,所以打印的是3:

8、执行 ILOAD 1,把局部变量表加载到栈顶:

9、执行INVOKEVIRTUAL java/io/PrintStream.println (I)V,此时栈顶元素为3,所以打印的是3:

完毕!
现在知道了吧,对于 i+++++i 的题也知道怎么做以及怎么回事了吧。
这篇文章重点让你理解 i++ 与 ++ i的实现机制,对于上面的汇编指令以及进栈入栈的过程为了更好说明要解决的问题,所以隐藏了很多细节,而且也删除了部分代码。如有错误的地方,还请见谅。
如果你想了解更多的汇编指令,我这里看到一篇总结的还挺全的:https://blog.csdn.net/hudashi/article/details/7062675。
声明:本文为作者投稿,版权归其个人所有。
热 文 推 荐
☞ 下一次 IT 变革:边缘计算(Edge computing)
☞ 年度重磅:《AI聚变:2018年优秀AI应用案例TOP 20》正式发布
print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!n");
cout < < "点个好看吧!" < < endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"
点击“阅读原文”,打开 CSDN App 阅读更贴心!

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 确保“十五五”开好局起好步 7904477
- 2 多所高校定制羽绒服卖爆 7808761
- 3 北京山村古刹飘雪 一夜穿越回千年前 7711987
- 4 2026年经济工作要这么干 7617973
- 5 白宫的回应让日本舆论失望了 7523041
- 6 一天到底吃几个鸡蛋最健康 7425824
- 7 哈尔滨5死29重伤事故调查报告公布 7328624
- 8 演员印小天在景区救下落水女子 7235848
- 9 日本海军侵华多张照片首度曝光 7141094
- 10 中央经济工作会议释放哪些重要信号 7047758







CSDN
