编译为 C 的语言中的智能指针

Smart Pointers in a language that compiles to C

我正在编写一种编译为 C 的简单语言,我想实现智能指针。不过,我需要一些帮助,因为我似乎无法想到我将如何解决它,或者它是否可能。我目前的想法是在超出范围时释放指针,编译器将处理插入释放。这引出了我的问题:

编译器是用 C 编写的,并编译为 C。我认为我可以在编译时检查指针何时超出范围,并在指针的生成代码中插入一个 free,即:

// generated C code.
int main() {
    int *x = malloc(sizeof(*x));
    *x = 5;
    free(x); // inserted by the compiler
}

作用域规则(在我的语言中)与 C 完全相同。

我目前的设置是你的标准编译器,首先它对文件内容进行词法分析,然后解析令牌流,对其进行语义分析,然后生成 C 代码。解析器是一个递归下降解析器。我想避免在执行时发生的事情,即我希望它是一个几乎没有开销的编译时检查,并且不是完全成熟的垃圾收集。

对于函数,每个{开始一个新的作用域,每个}关闭相应的作用域。当达到 } 时,该块内的变量超出范围。当结构实例超出范围时,结构成员也会超出范围。有几个例外,例如临时对象在下一个 ; 超出范围,编译器悄悄地将 for 循环放在它们自己的块范围内。

struct thing {
   int member;
};

int foo;

int main() {
   thing a;
   {
       int b = 3;

       for(int c=0; c<b; ++c) {
          int d = rand(); //the return value of rand goes out of scope after assignment
       } //d and c go out of scope here

   } //b goes out of scope here
}//a and its members go out of scope here
//globals like foo go out-of-scope after main ends

C++ 尝试确实 很难以相反的顺序销毁对象,您也应该用您的语言这样做。

(这都是我对 C++ 的了解,所以它可能 与 C 略有不同,但我认为不是)


至于内存,您可能想在幕后施展魔法。每当用户 malloc 内存时,您将其替换为分配 更多 内存的内容,并在额外的 space 中分配 "hide" 引用计数。在分配开始时这样做最简单,并且要保持对齐保证,您可以使用类似于此的方法:

 typedef union {
     long double f;
     void* v;
     char* c;
     unsigned long long l;
 } bad_alignment;

 void* ref_count_malloc(int bytes)
 {
     void* p = malloc(bytes + sizeof(bad_alignment)); //does C have sizeof?
     int* ref_count = p;
     *ref_count = 1; //now is 1 pointer pointing at this block
     return p + sizeof(bad_alignment);
 }

当他们复制一个指针时,你在复制之前默默地添加类似的东西

 void copy_pointer(void* from, void* to) {
     if (from != NULL) 
         ref_count_free(free); //no longer points at previous block
     bad_alignment* ref_count = to-sizeof(bad_alignment);
     ++*ref_count; //one additional pointing at this block
 }

当它们释放或指针超出范围时,您 add/replace 调用类似这样的东西:

 void ref_count_free(void* ptr) {
     if(ptr) {
         bad_alignment* ref_count = ptr-sizeof(bad_alignment);
         if (--*ref_count == 0) //if no more pointing at this block
             free(ptr);
      }         
 }

如果您有线程,则必须为所有线程添加锁。我的 C 生锈了,代码未经测试,所以对这些概念进行了大量研究。

问题稍微难一点,因为你的代码很简单,但是......如果另一个指针指向与 x 相同的位置怎么办?

// generated C code.
int main() {
    int *x = malloc(sizeof(*x));
    int *y = x;
    *x = 5;
    free(x); // inserted by the compiler, now wrong 
}

你无疑会有一个堆结构,其中每个块都有一个 header 告诉 a) 该块是否正在使用,以及 b) 块的大小。这可以通过一个小结构来实现,或者通过在 b) 的整数值中使用 a) 的最高位 [这是 64 位编译器还是 32 位编译器?]。为简单起见,让我们考虑:

typedef struct {
    bool allocated: 1;
    size_t size;
} BlockHeader;

您必须向那个小结构添加另一个字段,这将是一个引用计数。每次指针指向堆中的那个块时,您都会增加引用计数。当一个指针停止指向一个块时,它的引用计数就会减少。如果它达到 0,那么它可以被压缩或其他什么。 allocated 字段现已不再使用。

typedef struct {
    size_t size;
    size_t referenceCount;
} BlockHeader;

Reference counting 实现起来非常简单,但有一个缺点:这意味着每次指针值更改时都会产生开销。仍然是最简单的方案,这就是为什么一些编程语言仍然使用它,例如 Python.