虚拟机指令集&栈与函数调用(中)

简介: 虚拟机指令集&栈与函数调用(中)

image.png

add栈中保存了返回地址

参数一般放在main栈的最底下 也可以定义在add栈里面

main栈的栈基base point 即bp

局部变量ret

最后就是栈顶

当add调用结束之后 直接回到bp位置

所有的局部变量就不需要了

然后把add栈中的bp放到main栈中的bp中去

返回值给pc

然后代码区就会有一个跳转

这就是一个函数调用的过程

通过一个后进先出的一个空间的抽象

极大的简化

不同的函数栈之间的内存的关系

但不是一个非有不可的概念 只是这个概念大大简化了维护成本

是否一定要有函数调用呢

有一个Brain-Fuck语言 <>+=[],.

整个代码里面全都有这8种符号组成 可读性为0

读这个代码需要你的大脑很痛苦的运算

所以叫Brain-Fuck

这种语言其实就是假设有2个纸带

第一条纸带 装语言本身就是代码

第二条纸带 装数据的

有个探针只在这个数据的某个起始位置

小于号就是探针左移一格

大于号就是探针右移动一格

加号就是探针所指的这个数据的地方

默认值是0 加号就是把它加1变成1了

减号就是减1变成0了

左括号就是代码区会根据一个条件去判断是否要跳转

条件是你现在这个探针所指的位置是否等于0

如果不等于0 就直接执行

如果等于0 就跳转到对应的右括号

右括号的意思是当指针所指的位置数据等于0

它就直接继续执行

如果不等于0 就跳到对应的左括号

左括号相当于是定义的虚拟机指令的JZ

右括号相当于JNZ

逗号就是从IO设备输入一个数

句号就是输出一个数

如果基于这样简单的语法 如何实现两数相加

image.png

首先逗号输入一个数 探针在初始位置

就是data区的第一个位置

比如输入的是3

探针右移

然后又输入一个数 比如是4

然后一个左括号

判断探针所指的这个值是否为0

不为0就不跳转就进行下一位

下一位就是左移 探针又移回了

给它加1 ,3就变成了4

加1之后 探针右移

右移之后下一个代码是减

4-1=3

此时探针在3的位置 然后判断它是否为0

不为0 就跳转到对应的左括号

然后又继续执行左移

左移之后又继续加 4变成5

右移就是减 3变成2

又是循环跳转 直到减为了0

为0了之后 就不跳转了(JNZ)

执行下一个代码 EOF即结束了

最终的效果就是把4加到了3上面得到结果是7

这就是brain fuck语言 怎么实现一个加法的过程

这种语言是目前最接近图灵机的一种语言

所以没有函数调用也能完成这些复杂的运算

通过函数调用可以简化

但在brain fuck里面没有stack概念 如果想实现函数调用也是相当复杂的

如果多了一个纸带 那么就很容易实现函数调用

有了函数调用之后 整个主要逻辑的编码就会变得非常简单

所以就明白了 为什么要有代码区、数据区、stack区 这样的分区设计

c、执行完了之后 需要知道return结果的返回值

计算完之后 把结果存在某个寄存器中 比如约定存在通用寄存器ax中

再回到调用处的时候ax里面的值就是被调用函数的返回值

然后把ax赋值到ret

d、返回地址

函数调用(跳转)相关指令

  • CALL
  • RETURN
  • NVAR:new statck frame for variable
  • DARG:delete statck frame for argument

image.png

还是以上面 main调用add的方法举例

首先main在调用之前需要准备好两个参数

假设这里是stack区 从大到小

前面的main栈先不管了

但接下来的2个地方用来存放2个参数

假设一个是3一个是4

image.png

接下来调用add方法了

代码区的情况

image.png

image.png

main代码区和add代码区是code区中连续的代码区 为了方便说明 分开画的

add代码区的地址是430430

main代码区就是Call 430430

call完之后 会对这个地方做一个清理 DARG2

image.png

假设call位置的地址是430420

参数也会占用一个地址 430424

需要返回的这个地址是430428

执行完add方法之后 需要把pc寄存器返回到430428

执行下一条代码


image.png

image.png

pc就会等于430430这个地址里面的数

然后把sp下移了一位

结果存到了430428这个地址

image.png

把返回地址存进去了

执行完add之后 要返回的时候

通过这个地方就能知道要返回到哪了

接下来看看add函数中的vm指令

首先需要做nvar 给函数的局部变量申请stack frame的一些初始空间

这个初始空间首先要存bp地址

存的是老bp地址

因为一旦跳转了之后 进入这个新的栈 那这个新的位置就是bp了

老bp其实就是main的bp也得存进来

因为回头要恢复这个栈的原貌

相关文章
|
7月前
|
存储 Java 数据安全/隐私保护
【JVM】Java虚拟机栈(Java Virtual Machine Stacks)
【JVM】Java虚拟机栈(Java Virtual Machine Stacks)
116 0
|
7月前
|
存储 安全 Java
【数据结构】栈的使用|模拟实现|应用|栈与虚拟机栈和栈帧的区别
【数据结构】栈的使用|模拟实现|应用|栈与虚拟机栈和栈帧的区别
66 0
|
4月前
|
Java
程序计数器和虚拟机栈
这篇文章介绍了Java虚拟机(JVM)的内存结构,特别解释了程序计数器(Program Counter Register)的作用,即用来记录下一条JVM指令的执行地址和行号。
程序计数器和虚拟机栈
|
4月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
5月前
|
云计算
云计算问题之线程请求的栈深度大于虚拟机所允许的深度如何解决
云计算问题之线程请求的栈深度大于虚拟机所允许的深度如何解决
32 1
|
4月前
|
安全 Java
虚拟机栈的五道面试题
这篇文章提供了关于Java虚拟机栈的五个面试问题,涉及栈溢出的情况、栈大小调整、栈内存的分配、垃圾回收与虚拟机栈的关系以及局部变量的线程安全性。
|
5月前
|
存储 Java 对象存储
Java虚拟机(JVM)中的栈(Stack)和堆(Heap)
在Java虚拟机(JVM)中,栈(Stack)和堆(Heap)是存储数据的两个关键区域。它们在内存管理中扮演着非常重要的角色,但各自的用途和特点有所不同。
55 0
|
6月前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
67 2
|
7月前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
47 0
|
7月前
|
存储 缓存 Java
JVM【带着问题去学习 02】数据结构栈+本地方法栈+虚拟机栈+JVM栈运行原理
JVM【带着问题去学习 02】数据结构栈+本地方法栈+虚拟机栈+JVM栈运行原理
87 0