编译为 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.
我正在编写一种编译为 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.