我正在编写一种可编译为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略有不同,但我不认为是)
至于记忆,你可能会想在幕后施展一点魔法。每当用户对内存进行mallocs时,您都会将其替换为分配更多内存的东西,并在额外的空间中"隐藏"引用计数。在分配开始时最容易做到这一点,并且为了保持一致性保证,您可以使用类似于以下的东西:
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
}
当它们空闲或指针超出范围时,您可以添加/替换如下调用:
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
}
毫无疑问,您将拥有一个堆结构,其中每个块都有一个头,告诉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;
引用计数实现起来很简单,但也有缺点:这意味着每次指针值发生变化时都会有开销。尽管如此,这是最简单的工作方案,这就是为什么一些编程语言仍然使用它,比如Python。