请教一个高级语言中函数调用栈相关的问题

2022-08-30 16:50:34 +08:00
 yezheyu

最近在研究 JavaScript 中闭包,发现 A 函数返回值是其内部定义的 B 函数对象时,就算 A 函数执行完,因为词法环境的缘故,A 函数内部的局部变量也不会被销毁。

类似的 python 中闭包也有这个特性

而这个和我理解 C 语言或汇编的调用栈,一个函数执行完,调用栈中所有的变量都会被销毁,完全不一样

直到我看到别人博客中一句话:

“ 汇编或 C 语言中的栈和高级语言中栈是完全不同的。JavaScript 中闭包是在堆中申请的,使用 gc 管理,而汇编栈不存在 gc ,由函数调用与返回来自动更新 SP 指针实现的。所以在高级语言的闭包中其父级作用域在函数结束后不会被销毁 ”

他这句话对吗?高级语言的函数调用栈是都在堆中申请的吗?

function User(age){
    this.age = age
    this.changeAge = function(){
        this.age += 1
        console.log(this.age)
    }
}

let tom = new User(18)
tom.changeAge()  // 19
tom.changeAge()  // 20 ,而不是 19

所以 js 的构造函数可以反复使用方法修改属性就是借助闭包实现的是吗?

class User:
    def __init__(self, age):
        self.age = age
    def changeAge(self):
        self.age += 1
        print(self.age)

tom = User(18)
tom.changeAge()  // 19
tom.changeAge()  // 20 ,而不是 19

类似 python 的类的实现细节中,对象的方法可以反复修改对象的属性也是借助闭包实现是吗?

1829 次点击
所在节点    程序员
22 条回复
SmiteChow
2022-08-30 17:45:15 +08:00
闭包在堆上,不在栈上
SmiteChow
2022-08-30 17:46:38 +08:00
函数调用栈当然是在栈上,并不冲突
SmiteChow
2022-08-30 17:48:27 +08:00
你举的例子都是对象上的 member 操作,跟闭包沾不上边
yezheyu
2022-08-30 17:54:21 +08:00
@SmiteChow 像 js 的构造函数不就是用 function 关键字声明的,不就可以看成一种特殊函数吗?使用其方法,不就是使用其内部的函数,内部函数用到其外层的属性,这结构不就和闭包结构很相似吗
yezheyu
2022-08-30 17:59:49 +08:00
@SmiteChow
"闭包在堆上,不在栈上"
"函数调用栈当然是在栈上,并不冲突"

但是在 js 中所有函数不都是闭包吗?那闭包在堆上,函数执行在栈上,岂不是矛盾了?
secondwtq
2022-08-30 19:26:29 +08:00
什么堆啊栈啊的,C 标准里面压根没这俩词儿,JS 标准里面也没汇编里的栈。
DOLLOR
2022-08-30 19:40:16 +08:00
你举的这两个例子,都跟“闭包”没有关系呀。
闭包至少要有几个嵌套的作用域,或者说嵌套的函数。
momocraft
2022-08-30 19:46:52 +08:00
看得人想当堆栈警察 又没时间
Jooooooooo
2022-08-30 19:59:18 +08:00
这...完全是理解跑偏了吧. 你这问题和栈啊闭包啊一毛钱关系没有.

(虽然我不懂 js)

第二个值会变是因为操作的是同一个对象啊
zunceng
2022-08-30 20:05:07 +08:00
当年我还写蛋疼的 c++的时候 也玩闭包
变量定义成智能指针 lambda 上对这个 shared_ptr 值拷贝
不过和高级语言的 gc 性质不太一样而已
Track13
2022-08-30 20:12:34 +08:00
function foo() {
let a = 1
return function() {
console.log(a)
}
}
const c = foo()
c()

这才是闭包
lisongeee
2022-08-30 20:13:03 +08:00
> 所以在高级语言的闭包中其父级作用域在函数结束后不会被销毁

对于 v8 这个 js 引擎,如果子作用域使用了父作用域的变量,引擎会做静态代码分析,销毁父作用域后,把用到的变量包起来,打包给子函数,放到一个属性上

![image]( https://user-images.githubusercontent.com/38517192/187432264-4ffd2936-d763-4408-8443-55a2609a8016.png)
ScepterZ
2022-08-30 20:13:08 +08:00
你说的这个事和给的代码好像没一点关系
以 go 为例,编译器会做逃逸分析,如果变量有闭包之类后边还要用的情况,就照你说的,放到堆上,后边由 gc 回收,如果只是这里用,return 之后可以直接回收的话,就不会了
hangbale
2022-08-30 20:54:44 +08:00
这博客说的太模糊了,首先要先搞清楚运行时里的栈,堆是什么
另外,闭包=被闭包引用的值+ 代码,实现闭包关键要解决生命周期的问题
Al0rid4l
2022-08-30 20:59:00 +08:00
并不是函数嵌套就构成闭包, 而是嵌套的函数引用了外部的变量才构成闭包
yezheyu
2022-08-30 21:44:21 +08:00
@Al0rid4l 我看很多博客都有说所有函数都是闭包,假如把 js 脚本文件看成 main 函数,脚本内部定义的函数不就相当于内部函数,函数名不就至少是个外部变量的引用吗?

https://zh.javascript.info/closure
就像这篇博客结尾说的 v8 引擎优化
yezheyu
2022-08-30 21:52:42 +08:00
函数名这个例子好像举的不太对,不过就算没有对外层函数的引用,这会因为词法环境的缘故普通函数也会形成闭包吧
yezheyu
2022-08-30 22:01:30 +08:00
@hangbale

zh.javascript.info/closure

参考这篇博客,里面全篇没有提堆栈方面东西,所以像 js ,Python 这种跑在 Runtime 之上的软件,其调用栈规则其实和 C 的调用栈是没有参考性的是吗
yezheyu
2022-08-30 23:05:11 +08:00
@lisongeee
@ScepterZ

这样理解你们看对吗?
当内部函数被 return 出去后,外部函数执行完,其调用栈中所有变量都会被销毁,而因为内部函数又对外部函数中变量有引用,js 就会在堆区开启一块空间保存引用的数据,以保证外部函数的变量在销毁后任能获取到,而堆区中这块数据就会被 gc 接管
hangbale
2022-08-30 23:05:31 +08:00
@yezheyu 语言本身是没有堆栈一说,堆栈这两个东西的意义跟编译器如何分配程序中各种变量的内存空间以及管理它们的生命周期有关,所有的语言, 调用栈在运行形式上都是一样的,函数 outer 执行时, 它的活动记录入栈, 函数退出时 ,它的活动记录出栈,函数的局部数据也随之销毁,但是如果这个函数返回了一个函数 inner ,并且 inner 引用了 outer 的局部数据(闭包),那么 outer 的这些被引用的数据就不能被销毁,所以这些数据通常会移到堆上去,但是堆上的数据一般需要手动销毁或者写一个 GC 来管理。
js 与 c 不同的是,js 通常依赖 C++实现,所以在 js 里谈堆栈谈的是 C++的东西,另外 js 是动态类型,一切可变,这会很大程度上影响存储分配策略

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/876510

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX