撸了将近两个礼拜,撸了一个玩具编程语言的内核,也算对当初编译原理的老师有了交代。
想到一个问题,就是主流编程语言的函数调用都是怎么实现的,是否需要一个栈来保存状态(比如我的实现方法),如果是的话不用担心爆栈吗?
比如如果我要用类似这种调用
a = 1
def in():
print(a)
def out():
a = 2
in()
这里,执行out()
的话in
里调用的应该是a=2
而不是a=1
。
以我菜鸡的水平,我的实现方式是以把上文中的函数的 ast 拷贝一份,贴到调用的那个地方,本质上是压了个栈。那很自然程序员就会想到一个问题,如果极端情况下嵌套了很多函数调用呢,栈会不会爆掉?我们都知道比如 python 默认限制递归层数不能超过 1000.....
比如如果我要写一些函数式编程的代码 a.in().out().in().out().in()........
岂不是问题很大?
(不要跟我说函数式调用方式跟上文的例子根本不一样 orz )
2
crella 2020-03-04 00:44:51 +08:00 via Android
不知道是不是这个形式?
puts RUBY_VERSION class Hi attr_reader :v def initialize(v) @v=v end def a return Hi.new(@v+1) end def b return Hi.new(@v+2) end end script ="c" c=Hi.new(0) 100.times do script << ".a.b" end d=eval(script) puts d.v |
3
crella 2020-03-04 00:48:33 +08:00 via Android
不好意思,没看到是函数式编程,溜了溜了
|
4
agagega 2020-03-04 00:54:49 +08:00
主流的语言是栈实现的。要优化的思路也很简单直接:把栈去掉。所以可以考虑尾调用优化,或者协程类似的东西(想到什么说什么)
|
5
msg7086 2020-03-04 00:55:25 +08:00 via Android
这是作用域绑定的问题?
|
6
thedrwu 2020-03-04 00:55:47 +08:00 via Android
传统语言会爆栈,函数语言许多有尾递归优化。比如 Haskell 就重度依赖尾递归
|
7
dremy 2020-03-04 01:11:34 +08:00 via iPhone
可以从汇编语言层面分析,就很容易明白了
|
8
visitant 2020-03-04 01:37:04 +08:00 1
汇编语言分析+1,看看 CSAPP 吧.如果递归层次太多,当然会爆栈了, `ulimit -s`可以看 linux 下默认栈大小
|
9
251 2020-03-04 01:37:26 +08:00 via Android
原理是把机器码放进 CPU。无论什么东西大了都会爆吧
|
10
black11black OP |
11
ysc3839 2020-03-04 02:33:34 +08:00 via Android
x86 平台上 C 是直接用 call 指令实现的,而 call 又等价于 push 返回地址 + jmp。push 是把值压入栈中,所以肯定有可能爆栈。
|
12
CismonX 2020-03-04 03:35:10 +08:00
Q1:是否需要一个栈来保存状态?
A1:可以用,也可以不用。在很多函数式语言的实现中,用的是 CPS (Continuation Passing Style) 而非调用栈。 Q2:不用担心爆栈吗? A2:为了防止尾递归导致内存占用无限增长,需要进行 TCO (Tail Call Optimization),也就是我们熟知的尾调用优化。如果是基于调用栈的实现,那就在尾调用发生时跳转到函数首,而非分配一块新的栈帧。如果是基于 CPS 的实现,那就需要用到 Trampoline 来进行 TCO。 Q3:如果我要写一些函数式编程的代码,....,岂不是问题很大? A3:楼主举的例子不是很恰当,这种链式调用算不上“函数式写法”,而且本身也不会导致“爆栈”,因为后一个方法是在前一个方法执行完成并返回后才被调用的,当后一个方法被调用时,前一个方法调用时的栈帧已经被释放。 PS:正巧我前一阵子也在学习编译原理。我试着实现一个极简的函数式语言 Unlambda 来练手。目前广为流传的实现中多数都引入了 CPS,但我的实现没有采用,而是用了调用栈。楼主可以看我之前的一个帖子: https://www.v2ex.com/t/643109。欢迎与我来探讨。 |
13
loading 2020-03-04 08:10:46 +08:00 via Android
建议楼主学习下汇编
|
14
lance6716 2020-03-04 09:37:19 +08:00 via Android
可以不把 ast 拷贝,而是留在“代码段”并在编译时正确指示调用和返回的地址。
|
15
black11black OP |
16
251 2020-03-05 02:30:08 +08:00 via Android
调用深了,栈肯定会爆。主流语言默认情况不可能通过拷贝的方式调用函数,太占内存了。c ++ 有内联函数就是拷贝,这样执行效率会更高。
|