小谈 " j = j++ ; "

(底部有更新,更新的理解!)
对于 “j=j++;” 这一表达式,在高级语言Java/c/python等,这一层次,对语句进行分析。
其运行逻辑,最简单的理解为:
将j自加1,然后将 j 未自增的值赋予j本身。
不过从这个角度去分析,多少有些混乱,乱就乱在,j++操作本身,语义的不清晰。
语法学习中,对于j++操作的理解是:
先将j原值赋予外层表达式,然后将j自身增加1。
故而,以此 对 j=j++ 解析,将 j 原值赋予j,并将j自增1;(前半段还好理解,后半句的j到底是哪个j嘞,其实这正是关键,因为 在将java 源码 解释为 字节码后 ,对于这两个 j 其实 并不再指 同一个 j)
那么运算完成后,j到底是j原值还是自增后的值呢?若不理解jvm底层对java指令的实现,则只能猜测,java 语言定义了,j++就是将j原值赋予了j,所以j++被忽略。
这样当然不能满足我了,所以,翻出jvm相关书籍,开始深入分析,现总结如下。
《–》有很多前提知识,要先了解
1.比如编译原理对于高级语言的解析.
2.高级语言是要编译为低级语言才能被机器加载运行的.
3.对于具体情况还要具体分析,尤其是java来说,我就按照java的情况分析一下j=j++;被jvm加载后到底是如何运作的,并达到了什么作用。
在jvm内部,共分三个部分,执行引擎+类加载器子系统+运行时数据区;跟此处相关的是执行引擎和运行时数据区。
先说明一下默认的当前背景:
高级语言中,类成员方法内部的变量称为局部变量,无论是静态方法与否,jvm对变量的处理是不同的,他是一定的体系分层去处理的。这里我们只介绍下述这种情况:
————————————————————————————

public class test{
	public test(){
	}
	public static void main(String[]args){
			int i=0;
			i=i++;
	}
	public static void test1(){
		int i1=0,i2=0;
		int j=1;
		j+=2;
		i1=j++;
		j--;
		i2=++j;
	}
	public static void test2(){
		new test().test3();
	}		
	public void test3(){
		int i=1;
		i=i++;
		int j=2;
		j=++j;	
	}
	
}

代码是测试类,要配置好jdk,将java编译成class代码后,利用jdk自带的javap工具反汇编查看java字节码(
我在用javap时遇到了两个小情况,
一是即便配好了java在cmd中也无法调用javap指令,于是我将%JAVA_HOME%\bin添加到了path变量中;
二是调用javap时我先将java文件javac编译后会生成.class文件于当前路径,然而再调用javap指令时可能找不到这个.class文件,于是使用如下指令将类搜索路径更改为当前路径:->javap -cp . -c test.)
这个样就能看到.class文件中的字节码了,也就是jvm运行时加载的指令,当然落实到真正机器内部仍不是如此,可参考另一篇博客“从c到二极管”。但对于加载jvm运行的java语言来说,iload_1类似指令即是类汇编的助记符指令。main方法字节码指令
这时main方法的字节码指令列表,对于jvm来说他就是从上到下的顺序一条一条的执行的(对于特殊的跳转指令则不然) ,对应于mian方法内部

int i=0;
i=i++;
第一句iconst_0;//将真值 0 压入操作数栈中因为是int类型参数,指令开头为i。
第二句istore_1;//将操作数栈顶也就是刚刚压入的0值,弹出赋给局部变量数组[1]处的局部变量,也就是‘i’。
第三句iload_1;//直译为将局部变量数组[1]处的值传出赋给当前变量
这是关键:从第三句开始对应着java代码:i=i+1;jvm会这样做,在jvm内部,他会对每一个类的每一个方法维护三个逻辑数据区,这里涉及到的就是存储操作数的操作数栈,什么意思呢,当jdk在翻译java代码时,会进行一系列的操作来处理java指令,首先是进行词法以及句法分析,将每一行代码分割,分门别类的将个个字符子串解析为字节码对象,最后还将转化为一条条字节码指令,即操作符+操作码形式。具体如下:看到int i=0;这句话,会识别int i是声明了一个局部变量并将其放在另一个方法数据区"局部变量数组中",以此作为以后对他们的引用,然后对于‘=’,编译器javac会将其理解为一次赋值操作,留待后面执行,到了最后的‘0’,编译器会将其理解为一个int型的真值,并将其压入操作数栈中,也就是说,局部变量放在局部变量数组中存储,也就是将那块内存标识为相应变量,同样,将指令中的真值叫做操作数,并放入操作数栈中,将那块内存标记为某操作数,且其内部寄存器存储的就是,从java指令上读取解析来的真值’0‘。

小小结如下:那么对于int i=0;翻译为字节码指令就是:声明一个局部变量叫做i,将一个int型真值0压入操作数栈,[然后对于=,记为整条语句的操作码,也就是整条语句的谓语(“赋值”)]然后将0赋给i。在字节码中简记为 icont_0 ;似乎很简洁,其实则是,源于背后的整个底层系统支撑的。

 第二句完成后,在main方法的数据区中,就分别加载进来了两个东西,一个是局部变量数组[1]处的i,一个是真值0压在了操作数栈中(不过我不知istore_1是否将0 pop出栈了,按理说是的,因为0已经没用了,所以我是这样认为的,store指令就会将栈内的值pop出来放入对应标号局部变量数组[]某变量处)。
 对于第三句,其实直译是缺少主语的,即将var[1]的值load给谁?我的理解为因为原java代码i=i++;我们知道,从java高级语言层次去理解,这句话从‘=’i将语句分为了两部分,左边是变量i,右边则是一个表达式,于是jvm会先将表达式进行解析执行。也就是执行”i++“,着便要打破常识了,对于编程来说,要明白(尤其是c语言)我们操纵的一切运算,都是以变量更准确的说是数据来进行的,没有一个变量来存储中间结果,则无法完成一连串的运算。比如上述语句i=i++,就可以将其理解为一个赋值操作内嵌一个加法操作,那么java是将内部运算结果付给一个临时性变量作为中转媒介,也就说说将i++的值赋给这个临时变量记为‘g’,再将其放到原表达式中,代替i++,再进行运算即i=g;那么,为什么不直接将i++的结果赋给i呢而要多套一步。其实这根整个计算机内部底层实现有关,jvm虽说是虚拟机但也是按如此思路来的,当然会有很大差别。
 其实学过什么后缀表达式,前缀表达式等等的应该对此不陌生。举例如下:我们用i=i+1举例
[i,=,i,+,1],假设jvm将.java文件中的i=i+1读取为如前所示字符数组,那么,如何将此数组进行计算呢,很简单,
在读取表达式时,若是操作数就压入一个表达式栈中,比如先读到的是变量i,那么此时栈顶即为i;然后,下一个为一个操作符'=',但是”=“是双目运算符,表达式栈中最近只有1个不足以完成一次运算,所以,将‘=’入栈继续解析,下一个是i这时,栈中已经有一个操作符两个操作数了,但我们的数学尝识告所我们,表达式计算是有优先级的,=操作可以说是最低优先级的,所以,继续向后读取(这里说明一下,关于表达式解析我的说明并不完全正确,甚至说基本不对,所以专门去搜搜就好了,这里只是解释一下为什么要有一个临时变量要存储中间结果,以及这个临时变量到底是什么)这时表达式栈如下:i,=,i
读入下一个+,同样,栈中最近只有1个操作数,+入栈,当读取到 1 时,完成了对表达式的读取,这时 1 不再入栈,而是将+和i取出,进行运算,得到(i+1)结果这里记为g,得到的g会继续作为表达式的一部分进行解析,这时,表达式栈中还有一个操作数一个操作符,于是取出=取出i,进行计算i=g。

***回归(i=i++)解释为java字节码就是:(重点)
iload_1;-> iinc 1,1;->istore_1
解释如下:
将局部变量[1]也就是 i 的值赋值给某个变量记为g(一会解释’某’),->
将 i 的值增1,也就是将局部变量数组[1]处的那个i加1,->
将 g 的值回弹给 i。
也就是说自增操作是运行在i上的,当g(记录i++结果的中间变量)回弹也就是‘=’操作不但没有将我们原以为的g自增后的值赋给 i ,反而是 未曾变化仅仅是 i 初始值的副本覆盖了自增后的 i.
接下来,我说说某的事情。
其实当i++运算完成后,记录他的中间变量,我记做了 g 那么他在哪里呢?其实,就是当前操作数栈栈顶,换个例子,i=i+1 则是说,jvm在计算时,先从操作说数栈中读取到1,并与+,i放在一起组成iinc 1,1指令,得到的结果直接压入操作数栈,也就是说g=i+1,g在操作数栈顶。
这时,对于赋值操作,i=?,?会直接从操作数栈顶取值,也就正好是g了,也就是说,其实,并没有一个概念上的中间变量,二是jvm的指令运算逻辑系统本身,也就是,iload、istore配合使得,似乎有了g这么一个中间变量,不过理解开来,就知道了,数据到底是怎么变化传递的,而计算机内层底部,就是数据的来回倒腾,当我觉得i=i++,结果i=0,而非i=1;时,并不是谁错了,而是,此 i 非彼 i,所以,想通运算细节,从而认清,到底哪个 i !

**

大总结:

**

在方法内部,我们所写的java代码,并不能被jvm识别,所以先要转换成机器码(对应jvm的字节码,由jvm加载运行),才能在cpu上运行。
人们用助记符,对机器码做了初步转义,帮助程序员编程,对应java的字节码同样的,是能被jvm理解并直接加载运行的指令序列。
jvm通过对类似iload、istore的支持,完成上层i++,i--等的类似高级语言指令。而对于jvm的指令系统,其实,本质就是对数据的变换,从哪个寄存器取出,运算一下,放到哪里去,再取下一个,再算再移动,如此往复,构成了整个通用计算机体系。
所以说,当system.out.print(i)输出 i 时,我们要认清,这个i到底对应内存中哪个 i ,这就要,仔细辨清,程序到底进行了什么,jvm到底把i放在哪里,又干了什么,怎么干的。
参考《深入java虚拟机》第5章,计算机组成原理相关知识,编译原理相关知识以及各博客。jvm不同之一就在于他的实现可能没有真正计算机底层的通用寄存器组,所以用操作数栈来维护运算。
参考博客:
http://blog.csdn.net/cser_coding/article/details/12143405
http://blog.csdn.net/lsbhjshyn/article/details/9329339

附后继两张字节码
test1

test3

比较学习嘛!
2016年10月5日再思考
10月5日再学习
对其有了更深入的理解,
关键就在icount_?,iload_?,istore_?三条命令的理解还包括iadd,iinc ?,?几条命令的理解。运算是通过iadd和iinc完成的,而赋值是通过操作数栈完成的。具体如下:
对于i=i++;就会是先iload_1,将局部变量1的值入操作数栈,然后iinc 1,1;即将局部变量1的值加1,然后istore_1将栈顶值弹出赋给局部变量1;
而对于i=++i;
则是先iinc 1,1;先将局部变量1的值加1,然后iload_1将局部变量1的值入栈,再然后istore_1将栈顶值弹出赋给局部变量1.
对这几条命令理解透彻,自然就明白一切了,jvm真正运行的是这样的代码,而这些代码到底是如何实现高级语句语义的。
再解释一下iadd,这也很关键。
比如int i=0;i=i++ + ++i;(注意空格要有)
这时,先是i++被运行:iload_1;iinc 1,1;然后++i被运行:iinc 1,1;iload_1;这时,局部变量区(i)对应的的值为2,而操作数栈上有了两个数第一个是i++时压入的0,他在栈底,第二个是++i时传入的2他在栈顶,这时对于(i++)和(++i)两部操作中间的"+"将被解释为 iadd,他将会从操作数栈顶上顺序依次连续取出2个操作数,也就是2,0然后进行2+0的操作,同时,得到的结果2将会被再次压入弹出了2,0后的空操作数栈顶,再然后,再执行istore_1将栈顶值赋给局部变量1处,完成全部操作。
这样基本就明白了大致原理,再扩展就不怕了,所以,在模拟jvm运算时,要时刻想着操作数栈上的情况,和局部变量区的情况,这就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值