代码之家  ›  专栏  ›  技术社区  ›  Cosmos Man

在闭包中,指针或引用类型存储在内存中或在现代函数语言中如何处理可变数据?[已关闭]

  •  2
  • Cosmos Man  · 技术社区  · 7 年前

    我正在写一本教育用的传送器。 我的transpiler从我的语言转换为C语言。
    我现在正在编写闭包语法分析器和代码生成组件。

    我看到有人说C++中的闭包实际上被转换为未命名的结构类型,其中包含捕获的值作为变量。

    Here is the reference .

    此代码

    int c = 10;
    auto closure = [=] () -> void {
        std::cout << c << std::endl;
    };
    

    他们说,基本上是在引擎盖下转化成这样的东西。

    struct UNNAMED_TYPE_0 {
        int c;
    
        void operator() () const {
            std::cout << c << std::endl;
        }
    };
    // assume the closure is initialized and variables are assigned
    

    如果有人想改变它 int c 执行闭包时,他/她必须将此变量作为ref传递 [&c] () -> void { /* mutation comes here */} . 但问题是如果我们宣布 内景c 在函数内部创建闭包,如下所示

    function<void()> aFunction() {
        int c = 10;
        auto closure = [&c] () -> void { c = 100; }
        return closure;
    }
    
    aFunction() ();
    

    内景c 被抓获,但一旦 aFunction 堆栈被破坏 内景c 也被摧毁了。这意味着,如果我们尝试在释放的地址上写入,我们可能会运行 segmentation fault(core dumped) 希望指针错误。

    在里面 Java语言 ,

    // suppose this callback interface exists
    public interface VoidCallback {
        public void method();
    } 
    
    
    public void aMethod() {
        int c = 10;
        VoidCallback callback = () -> c = 10; /* this gives an error */
        // local variables referenced from a lambda expression must be final or effectively final
    }
    

    Java处理这样的闭包,并确保闭包捕获(比如隐式捕获)不会发生变化。这意味着Java传递闭包捕获副本而不是引用。对于引用或类类型,只有对象指针作为副本传递。尽管指针引用不会发生变化,但您可以在指针指向的对象内变化内容。这与前一个基本相同。

    在里面 目标-C ,

    __block int c = 0;
    // they say, this `int c` is allocated in heap instead of stack
    // so that it exists until the closure is finished executing.
    void (^ closure) (void) = ^void() {
        c = 10; // this is valid
        // this actually changed the `int c`'s value
    }; 
    

    在里面 敏捷的

    var a : Int = 10;
    var closure = { [] () -> Void in
        a = 10; // this is valid by default
        // unless `var a` is declared as `let a`
    };
    

    因此,这意味着Objective-C和Swift将原语捕获列表分配为指针。这样它们就可以变异。

    P、 S:请注意,Swift闭包捕获列表仅适用于类或引用类型,但这里我指的是对基元类型的隐式捕获。

    这是

    __块int c=0;
    //他们说,这个int c是在堆中而不是在堆栈中分配的
    //直到闭包完成执行为止。
    void(^闭包)(void)=^ void(){
    c=10;//这是有效的
    //这实际上改变了“int c”的值
    };
    

    与此(基本上)相同

    int * c = malloc(sizeof(int));
    *c = 0;
    void (^ closure) (void) = ^void() {
        *c = 10;
        if (c) {
            free(c);
            c = NULL;
        }
    }; 
    

    我认为,在闭包完成后立即释放指针变量将太糟糕了。
    如果有很多闭包指向该变量,并且在执行时会发生变化,该怎么办?
    如果这些闭包是在不同的线程之间传递或执行的呢?

    我提出了一个使用引用计数技术的解决方案。
    当创建一个改变变量的闭包时,该变量将被保留。
    当变异变量的闭包被破坏时,变量将被释放。
    当没有闭包时,变量将真正被释放。 为了确保线程安全,我会在闭包操作引用计数技术时锁定和解锁计数器变量地址。

    如果还有其他技巧,请指导我。
    任何语言的解释都将不胜感激。

    目前,我对汇编语言一无所知。

    对于主持人, 由于这个问题是一种研究,我恳请你不要太宽泛。

    1 回复  |  直到 7 年前
        1
  •  2
  •   Ulrich Eckhardt    7 年前

    我突然想到:“我正在编写一个用于教育目的的Transpiler。我的Transpiler可以将我的语言转换为C语言。”现在,这意味着您的语言规范定义了它应该如何操作!我们无法告诉你你的语言应该如何运作。

    现在,您已经找到了一系列选项:

    • C++对局部变量没有任何特殊的处理。如果您保留对它的引用,并在它超出范围时使用它,则会带来厄运。这就是C++的精神,它不会给你增加任何开销,而是允许你在不注意的情况下开枪打自己的脚。
    • Java只需检查代码,并告诉您是否尝试执行它认为不一定有效的任何操作,否则会给您一个错误。它不允许你射中自己的脚,即使你非常想要它。
    • 其他语言似乎将范围有限的局部变量转换为基于堆的局部变量。我不确定它们的对象模型,但例如,在Python中,您根本没有任何类似于C++局部变量或Java基元类型的东西,就像您没有确定性析构函数调用一样(您可以使用 with 这里,只是为了完整性),所以这没有任何区别。这些语言会给您带来一定的开销,以确保您没有任何悬而未决的引用(也许即使您真的不需要它)。

    现在,首先要确定哪一个最适合您的语言的对象模型。只有到那时,问题才出现,如何最好地实现它。关于实现,有许多不同的方法。使用引用计数器是一种方法(尽管使用无锁原子操作实现),使用链表是另一种方法,或者使用垃圾收集器。