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设备输入一个数
句号就是输出一个数
如果基于这样简单的语法 如何实现两数相加
首先逗号输入一个数 探针在初始位置
就是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
还是以上面 main调用add的方法举例
首先main在调用之前需要准备好两个参数
假设这里是stack区 从大到小
前面的main栈先不管了
但接下来的2个地方用来存放2个参数
假设一个是3一个是4
接下来调用add方法了
代码区的情况
main代码区和add代码区是code区中连续的代码区 为了方便说明 分开画的
add代码区的地址是430430
main代码区就是Call 430430
call完之后 会对这个地方做一个清理 DARG2
假设call位置的地址是430420
参数也会占用一个地址 430424
需要返回的这个地址是430428
执行完add方法之后 需要把pc寄存器返回到430428
执行下一条代码
pc就会等于430430这个地址里面的数
然后把sp下移了一位
结果存到了430428这个地址
把返回地址存进去了
执行完add之后 要返回的时候
通过这个地方就能知道要返回到哪了
接下来看看add函数中的vm指令
首先需要做nvar 给函数的局部变量申请stack frame的一些初始空间
这个初始空间首先要存bp地址
存的是老bp地址
因为一旦跳转了之后 进入这个新的栈 那这个新的位置就是bp了
老bp其实就是main的bp也得存进来
因为回头要恢复这个栈的原貌