关于i++的疑问

通过JVM javap -c 查看字节码执行步骤了解了i++之后,衍生了一个问题:

int num1=50;

num1++*2执行的是imul(将栈顶两int类型数相乘,结果入栈),

那么

  1. 1. 计算机是如何计算的?
  2. 2. 为什么是栈顶的两个数相乘?
  3. 3. 为什么这样规定?
  4. 4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?
  5. 5. 计算完之后这个栈有发生了什么?

回顾什么是机器数、真值、原码、反码、补码

此处完全转载知乎博文:原码、反码、补码?这样理解很简单

1、机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,用一个数的最高位存放符号, 正数为0, 负数为1.

比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011

那么,这里的 00000011 和 10000011 就是机器数。

2、真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3

而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值

  1. 0000 0001的真值 = +000 0001 = +1
  2. 1000 0001的真值 = 000 0001 = 1

3、原码

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

  1. [+1]原 = 0000 0001
  2. [-1]原 = 1000 0001

因为第一位是符号位,

所以8位二进制数的取值范围就是:

  1. [1111 1111 ,0111 1111]
  2. [-127 , 127]

原码是人脑最容易理解和计算的表示方式.

4、反码

正数的反码是其本身

负数的反码是在其原码的基础上,

符号位不变,其余各个位取反.

  1. [+1] =[00000001]原 = [00000001]反
  2. [-1] =[10000001]原 = [11111110]反

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.

5、补码

正数的补码就是其本身

负数的补码是在其原码的基础上,

符号位不变, 其余各位取反,

最后+1. (即在反码的基础上+1)

  1. [+1] = [00000001]原
  2. = [00000001]反
  3. = [00000001]补
  4. [-1] =[10000001]原
  5. = [11111110]反
  6. = [11111111]补

对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值

计算机是如何计算的?

  1. 在计算机中,数字都是用补码来存储的,而一个字节的数字,规定1000 0000就是-128

1.假如计算机采用原码计算:

  1. 1-1=1+(-1)=0000 0001[原] + 1000 0001[原]
  2. =1000 00010[原]
  3. =-2
  4. 计算机中每个字节byte8位,最高位是符号位

但是结果是-2,明显按照这种计算方式(人的计算方式)存在问题,这种原码计算方式在人看来很好区分,但是机器不行.这也就是为何计算机内部不使用原码表示一个数

2.假如计算机采用反码计算:

  1. 1-1=1+(-1)
  2. =0000 0001[原] + 1000 0001[原]
  3. =0000 0001[反] + 1111 1110[反]
  4. =1111 1111[反]
  5. =1000 0000[原]
  6. =-0

结果正确,但是0带符号是没有任何意义的. 而且会有0000 0000[原]和1000 0000[原]两个编码表示0

3.假如计算机采用补码计算:

  1. 1-1=1+(-1)
  2. =0000 0001[原] + 1000 0001[原]
  3. =0000 0001[反] + 1111 1110[反]
  4. =0000 0001[补] + 1111 1111[补]
  5. =0000 0000[补]
  6. =0000 0000[原]

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128

  1. (-1) + (-127)
  2. = [1000 0001]原 + [1111 1111]原
  3. = [1111 1111]补 + [1000 0001]补
  4. = [1000 0000]补

-1-127的结果应该是-128, 在用补码运算的结果中, 1000 0000[补] 就是-128

但是实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示

so,使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数.

这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]


分析下乘法计算a乘以x

计算机中乘法计算的规则如下

  1. 对于计算机而言,左移一位代表乘以2,右移一位代表除以2。所以,对于a乘以x而言,只是将a左移x1的位并累加即可

举例:8*9= 0000 1000 * 0000 1001

  1. 9的第1位是1,\(1*2^0\),so,8左移0位,结果是 0000 1000=8
  2. 9的第2位是0,\(0*2^1\),so,位数为0,不运算,结果是 0=0
  3. 9的第3位是0,\(0*2^2\),so,位数为0,不运算,结果是 0=0
  4. 9的第4位是1,\(1*2^3\),so,8左移3位,结果是 0100 0000=64

    累加后 8+0+0+64=72

那么补码一位乘???除法?

尚未掌握,留待以后...

运行时栈帧分析

在这之前先看一段关于JVM虚拟机栈的描述:(from《深入理解Java虚拟机》2.2.3)

  1. 与程序计数器一样,JVM虚拟机栈也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是Java
  2. 法执行的内存模型,每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链
  3. 接、方法返回地址等信息。每一个方法从调用直至执行完的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

ok,让我们来分析下运行时栈帧结构


栈帧结构包含以下几种:

1.局部变量表

2.操作数栈

又叫操作栈。

  当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写人和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。

 

  举个例子,整数加法的字节码指令 iadd 在运行的时候操作数栈中最接近栈顶的两个元素已经存人了两个 int 型的数值,当执行这个指令时, 会将这两个 int 值出栈并相加,然后将相加的结果人栈。

  

3.动态链接

4.方法返回地址

  无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执背状态。一般来说,方法正常退出时,调用者的PC 计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

  

  方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:

  1. 恢复上层方法的局部变量表和操作数栈,
  2. 把返回值(如果有的话)压入调用者栈帧的操作数中,
  3. 调整PC计数器的值以指向方法调用指令后面的一条指令等。

5.附加信息

此时再来看之前的几个问题

2. 为什么是栈顶的两个数相乘?

3. 为什么这样规定?

4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?

5. 计算完之后这个栈有发生了什么?


下面通过分析运行时操作数栈和局部变量的编号来解释上边的几个问题(from 深入Java虚拟机8.4.1)

程序:

  1. public class StrTest {
  2. public int clac(){
  3. int a=100;
  4. int b=200;
  5. int c=300;
  6. return (a+b)*c;
  7. }
  8. }

javap -c StrTest.class得到 字节码指令

  1. D:\notepadd++cache>javap -c StrTest.class
  2. Compiled from "StrTest.java"
  3. public class StrTest {
  4. public StrTest();
  5. Code:
  6. 0: aload_0
  7. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  8. 4: return
  9. public int clac();
  10. Code:
  11. 0: bipush 100
  12. 2: istore_1
  13. 3: sipush 200
  14. 6: istore_2
  15. 7: sipush 300
  16. 10: istore_3
  17. 11: iload_1
  18. 12: iload_2
  19. 13: iadd
  20. 14: iload_3
  21. 15: imul
  22. 16: ireturn
  23. }

分析图:

   

      

   

总结

2. 为什么是栈顶的两个数相乘?

两数相乘是从操作数栈栈顶取数进行计算

3. 为什么这样规定?

why? 可能要很久以后才会理解或者想通吧!!!

4. 计算完之后这个栈栈顶往下是100 2 50 三个数吗?

计算相乘时两个数字从栈顶弹出,并把计算记过推入栈顶,所以栈中只有100

5. 计算完之后这个栈有发生了什么?

计算完成后此栈帧退出,把返回值压入调用这个方法的操作数栈


没想到一个自增搞出这多事。

计算机是如何计算的、运行时栈帧分析(神奇i++续)的更多相关文章

  1. Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)

    Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...

  2. 深入理解java虚拟机(十) Java 虚拟机运行时栈帧结构

    运行时栈帧结构 栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素.每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程. 每一个栈帧在编 ...

  3. java虚拟机规范-运行时栈帧

    前言 java虚拟机是java跨平台的基石,本文的描述以jdk7.0为准,其他版本可能会有一些微调. 引用 java虚拟机规范 java虚拟机规范-运行时数据区 java内存运行时的栈帧结构 java ...

  4. 【转载】深入理解Java虚拟机笔记---运行时栈帧结构

    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...

  5. x86_64 Linux 运行时栈的字节对齐

    前言 C语言的过程调用机制(即函数之间的调用)的一个关键特性(起始大多数编程语言也是如此)都是使用了栈数据结构提供的后进先出的内存管理原则.每一个函数的栈空间被称为栈帧,一个栈帧上包含了保存的寄存器. ...

  6. c函数调用过程原理及函数栈帧分析

    转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比 ...

  7. Java运行时数据区域分析

    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结 ...

  8. Java 虚拟机中的运行时数据区分析

    本文基于 JDK1.8 阐述分析 运行过程 我们都知道 Java 源文件通过编译器编译后,能产生相应的 .Class 文件,也就是字节码文件.而字节码文件通过 Java 虚拟机中的解释器,编译成特定机 ...

  9. 图文并茂-超详解 CS:APP: Lab3-Attack(附带栈帧分析)

    CS:APP:Lab3-ATTACK 0. 环境要求 关于环境已经在lab1里配置过了.lab1的连接如下 实验的下载地址如下 说明文档如下 http://csapp.cs.cmu.edu/3e/at ...

随机推荐

  1. windows系统下hosts文件的改写(为了测试nginx内网的证书代理,需要做域名解析)

    1. win加R     C:\WINDOWS\system32\drivers\etc 2.打开hosts文件  加入一行  IP为客户机要访问的IP地址  域名也是在nginx中定义好的 3.ct ...

  2. 吴裕雄--天生自然ORACLE数据库学习笔记:SQL语言基础

    select empno,ename,sal from scott.emp; SELECT empno,ename,sal FROM scott.emp; selECT empno,ename,sal ...

  3. static在c\c++中的作用(翁恺c++公开课[28-29]学习笔记)

    static相对来说是一个较复杂的修饰符,c++中的static在c的基础之上又包含了static在类中的应用(也就是说多了static的成员变量和static的成员函数):c\c++中静态变量.对象 ...

  4. 02-07Android学习进度报告七

    今天主要学习了关于Android开发的Date和Time组件部分内容. 首先看TextClock: 可以通过调用:TextClock提供的is24HourModeEnabled()方法来查看,系统是否 ...

  5. mysql8 Could not retrieve transation read-only status server

    想回顾下ssm,看着网上别的帖子手动搭了一个,一直报Could not retrieve transation read-only status server , java.sql.SQLExcept ...

  6. QCMS代码审计:XSS+SQL+后台getshell

    qcms是一款比较小众的cms,最近更新应该是17年,代码框架都比较简单,但问题不少倒是... 网站介绍 QCMS是一款小型的网站管理系统.拥有多种结构类型,包括:ASP+ACCESS.ASP+SQL ...

  7. A easy and simple way to establish Oracle ADG

    Yes, thanks to Then, I can give simple and reasy way to make it. Suppose hosts and IPs like that: 15 ...

  8. Django 学习之Django Rest Framework_序列化器_Serializer

    作用: 1.序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串. 2.反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型. 3 ...

  9. 【剑指Offer面试编程题】题目1524:复杂链表的复制--九度OJ

    题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点). 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第 ...

  10. 微信小程序(基础)

    文档官网:https://developers.weixin.qq.com/miniprogram https://developers.weixin.qq.com/miniprogram/dev/f ...