V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yezheyu
V2EX  ›  程序员

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

  •  1
     
  •   yezheyu · 2022-08-30 16:50:34 +08:00 · 1616 次点击
    这是一个创建于 598 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在研究 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 的类的实现细节中,对象的方法可以反复修改对象的属性也是借助闭包实现是吗?

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

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

    (虽然我不懂 js)

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

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

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

    zh.javascript.info/closure

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

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

    你当然也可以不返回回调函数,而是返回一个 class,就像下面这样:
    class C {
    private int tmp;

    public C(int tmp) {
    this.tmp = tmp;
    }

    public callback() {
    print(tmp);
    }
    }

    function foo() C {
    c = new C(1);
    return C;
    }
    foo().callback();

    达成的效果其实是一样的,返回的闭包函数其实就等价于一个带有私有属性和一个 method 的 class 。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1019 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 19:26 · PVG 03:26 · LAX 12:26 · JFK 15:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.