关于i++的疑问
通过JVM javap -c 查看字节码执行步骤了解了i++之后,衍生了一个问题:
int num1=50;
num1++*2执行的是imul(将栈顶两int类型数相乘,结果入栈)
,
那么
1. 计算机是如何计算的?
2. 为什么是栈顶的两个数相乘?
3. 为什么这样规定?
4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?
5. 计算完之后这个栈有发生了什么?
回顾什么是机器数、真值、原码、反码、补码
此处完全转载知乎博文:原码、反码、补码?这样理解很简单
1、机器数
一个数在计算机中的二进制表示形式,叫做这个数的机器数
。机器数是带符号的,用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011
那么,这里的 00000011 和 10000011 就是机器数。
2、真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3
而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值
。
0000 0001的真值 = +000 0001 = +1
1000 0001的真值 = –000 0001 = –1
3、原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
因为第一位是符号位,
所以8位二进制数的取值范围就是:
[1111 1111 ,0111 1111]
即 [-127 , 127]
原码是人脑最容易理解和计算的表示方式.
4、反码
正数的反码是其本身
负数的反码是在其原码的基础上,
符号位不变,其余各个位取反.
[+1] =[00000001]原 = [00000001]反
[-1] =[10000001]原 = [11111110]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.
5、补码
正数的补码就是其本身
负数的补码是在其原码的基础上,
符号位不变, 其余各位取反,
最后+1. (即在反码的基础上+1)
[+1] = [00000001]原
= [00000001]反
= [00000001]补
[-1] =[10000001]原
= [11111110]反
= [11111111]补
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值
计算机是如何计算的?
在计算机中,数字都是用补码来存储的,而一个字节的数字,规定1000 0000就是-128
1.假如计算机采用原码
计算:
1-1=1+(-1)=0000 0001[原] + 1000 0001[原]
=1000 00010[原]
=-2
计算机中每个字节byte有8位,最高位是符号位
但是结果是-2,明显按照这种计算方式(人的计算方式)存在问题,这种原码
计算方式在人看来很好区分,但是机器不行.这也就是为何计算机内部不使用原码表示一个数
2.假如计算机采用反码
计算:
1-1=1+(-1)
=0000 0001[原] + 1000 0001[原]
=0000 0001[反] + 1111 1110[反]
=1111 1111[反]
=1000 0000[原]
=-0
结果正确,但是0带符号是没有任何意义的. 而且会有0000 0000[原]和1000 0000[原]两个编码表示0
3.假如计算机采用补码
计算:
1-1=1+(-1)
=0000 0001[原] + 1000 0001[原]
=0000 0001[反] + 1111 1110[反]
=0000 0001[补] + 1111 1111[补]
=0000 0000[补]
=0000 0000[原]
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128
(-1) + (-127)
= [1000 0001]原 + [1111 1111]原
= [1111 1111]补 + [1000 0001]补
= [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, 1000 0000[补] 就是-128
但是实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示
so,使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数.
这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]
分析下乘法计算a乘以x
计算机中乘法计算的规则如下
对于计算机而言,左移一位代表乘以2,右移一位代表除以2。所以,对于a乘以x而言,只是将a左移x为1的位并累加即可
举例:8*9= 0000 1000 * 0000 1001
- 9的第1位是1,\(1*2^0\),so,8左移0位,结果是 0000 1000=8
- 9的第2位是0,\(0*2^1\),so,位数为0,不运算,结果是 0=0
- 9的第3位是0,\(0*2^2\),so,位数为0,不运算,结果是 0=0
- 9的第4位是1,\(1*2^3\),so,8左移3位,结果是 0100 0000=64
累加后 8+0+0+64=72
那么补码一位乘???除法?
尚未掌握,留待以后...
运行时栈帧分析
在这之前先看一段关于JVM虚拟机栈
的描述:(from《深入理解Java虚拟机》2.2.3)
与程序计数器一样,JVM虚拟机栈也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是Java方
法执行的内存模型,每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链
接、方法返回地址等信息。每一个方法从调用直至执行完的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
ok,让我们来分析下运行时栈帧结构
栈帧结构包含以下几种:
1.局部变量表
2.操作数栈
又叫操作栈。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写人和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
举个例子,整数加法的字节码指令 iadd 在运行的时候操作数栈中最接近栈顶的两个元素已经存人了两个 int 型的数值,当执行这个指令时, 会将这两个 int 值出栈并相加,然后将相加的结果人栈。
3.动态链接
4.方法返回地址
无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执背状态。一般来说,方法正常退出时,调用者的PC 计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:
恢复上层方法的局部变量表和操作数栈,
把返回值(如果有的话)压入调用者栈帧的操作数中,
调整PC计数器的值以指向方法调用指令后面的一条指令等。
5.附加信息
此时再来看之前的几个问题
2. 为什么是栈顶的两个数相乘?
3. 为什么这样规定?
4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?
5. 计算完之后这个栈有发生了什么?
下面通过分析运行时操作数栈和局部变量的编号来解释上边的几个问题(from 深入Java虚拟机8.4.1)
程序:
public class StrTest {
public int clac(){
int a=100;
int b=200;
int c=300;
return (a+b)*c;
}
}
javap -c StrTest.class得到 字节码指令
D:\notepadd++cache>javap -c StrTest.class
Compiled from "StrTest.java"
public class StrTest {
public StrTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int clac();
Code:
0: bipush 100
2: istore_1
3: sipush 200
6: istore_2
7: sipush 300
10: istore_3
11: iload_1
12: iload_2
13: iadd
14: iload_3
15: imul
16: ireturn
}
分析图:
总结
2. 为什么是栈顶的两个数相乘?
两数相乘是从操作数栈栈顶取数进行计算
3. 为什么这样规定?
why? 可能要很久以后才会理解或者想通吧!!!
4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?
计算相乘时两个数字从栈顶弹出,并把计算记过推入栈顶,所以栈中只有100
5. 计算完之后这个栈有发生了什么?
计算完成后此栈帧退出,把返回值压入调用这个方法的操作数栈
没想到一个自增搞出这多事。