为什么由于类型弱而在C中实现垃圾收集“不可能”?

时间:2009-01-10 20:43:21

标签: c memory-management garbage-collection

一个相当聪明的人告诉我你不能在C中实现垃圾收集,因为它的类型很弱。基本的想法似乎是C给你太多的自由。他提到了没有类型检查的铸造指针......

我并没有真正理解这个想法。有人可以给我一个解释,也可能是一个代码样本,说明为什么这样做不起作用。

注意:显然C是关于速度的,为什么要添加垃圾收集?我真的很好奇。

7 个答案:

答案 0 :(得分:24)

他可能提到了这样一个事实,即您可以将指针强制转换为int并返回原始指针类型。当你这样做时,GC几乎不可能正确清理,考虑:

char * p = (char *) malloc(16);
int i = (int) p;
p = 0;
// GC runs and finds that the memory is no longer referenced
p = (char *) i;
// p is now a dangling pointer

编辑:上面只会产生一个带有精确GC的悬空指针。正如其他人所指出的那样,保守的收集器仍然可以正确处理这种情况,因为它假设任何位模式可能是一个有效指针实际上是一个指针,因此不会释放分配的内存。然而,当进一步修改i以使其不再看起来像是指向收集器的有效指针时,这当然不再可能。如下:

char * p = (char *) malloc(16);
int i = ~((int) p);
p = 0;
// GC runs and finds that the memory is no longer referenced
p = (char *) ~i;
// p is now a dangling pointer

此外,(正如其他人所指出的那样)如果你想保留语言的全部功能,那么实现GC的GC是不可能的。如果您不使用上述技巧(即您将自己局限于可能的操作的子集),那么GC确实可行。

答案 1 :(得分:12)

完全有可能在C中实现你能想到的任何内存管理器。问题是你必须专门使用它的分配/释放函数,并将你的“指针魔法”限制在它可以跟踪的事物上。另外,内存管理可能仅限于某些受支持的类型。

例如,Objective-C的保留/释放系统和自动释放池基本上是用C实现的内存管理器。许多库也实现了自己的简单形式的内存管理,如引用计数。

然后,有Boehm垃圾收集器。要使用它,只需将您的malloc() / realloc()电话替换为Boehm版本,您就不必再次拨打free()。阅读the possible issues with this approach

另外,check this wikipedia page可以快速了解保守垃圾收集器的工作原理。

答案 2 :(得分:6)

如果您阅读了正确的论文并且您拥有CS的学士学位,那么为C实施一个体面的保守的垃圾收集器实际上非常容易 - 我有十几个学生已经完成了它作为一个大约四周的课堂练习。然后花20年时间改进它,你得到Boehm collector (libgc)

基本思路很简单:如果寄存器,堆栈,全局变量或实时堆对象中有位模式,那么位模式恰好是属于对象的地址分配了malloc,该对象被视为 live 。任何不活动的对象都不可能通过以下指针到达,因此可以回收它并用于满足将来的分配请求。这种技术在指针的硬件表示上运行,它完全独立于指针的类型 ---这里的类型无关紧要。

确实存在一个警告:保守的垃圾收集技术可以通过故意隐藏指针来欺骗。压缩包含指针的结构,将指针的唯一副本保留在磁盘上,通过异或0xdeadbeef对指针进行模糊处理,所有这些技术都会破坏保守的收集器。但除非故意这样做,否则这种问题极为罕见。优化编译器的作者通常小心不要隐藏这样的收集器的指针。

您问题中最有趣的部分是为什么要这样做。原因有三:

  • 它消除了许多内存管理错误的可能性。

  • 简化了您的API ,因为不再需要指定谁分配内存,谁拥有分配的内存,是否需要复制内存,以及谁负责释放内存。

  • 信不信由你,it can be faster而不是mallocfree

答案 3 :(得分:3)

问题在于运行时无法确定是否引用了任何内存。即使您在注册用法的代码中包装所有内存分配,您仍然可以通过常规指针操作(或错误地)获取指向已用内存的指针。强制转换只会使运行时更难解决问题。因此,如果运行时释放了一块内存,那么对于仍指向该内存区域的任何指针,它都会搞砸。当你认为垃圾收集必须也适用于多线程应用程序时,情况显然会变得更糟。

答案 4 :(得分:3)

实现C的垃圾收集器并不是不可能(事实上,它们确实存在,就像简单的google search所揭示的那样),这很难,因为它很难确定某个位串 是指向已分配区块的指针,还是看起来像一样。

这是一个问题的原因是因为C(和C ++,就此问题)允许您从指针类型转换为整数类型,因此整数变量可能在分配的块中保存一个地址,从而阻止GC释放该块,即使该值不是指针。

例如,假设我分配了一块内存。假设这个内存块从地址0x00100000(1,048,576)开始分配,长度为1 MB,因此扩展到0x001FFFFF(2,097,151)。

假设我也将图像文件的大小存储在变量中(让我们称之为fileSize)。此映像文件恰好是1.5 MB(1,572,864字节)。

所以当垃圾收集器运行时,它会遇到我的fileSize变量,发现它包含一个与我分配的块中的地址相对应的值,并确定它不能释放这个块,以免它使我失效也许指针。那是因为GC不知道我是否已经这样做了:

int fileSize;
{
    char *mem = (char*)malloc(1048576);
    fileSize = (int)(mem + 524288);
}
// say GC runs here

或者如果我刚刚这样做了:

int fileSize;
{
    char *mem = (char*)malloc(1048576);
    fileSize = 1572864;
}
// say GC runs here;

在后一种情况下,可以安全地将块释放到* mem,(如果没有其他引用存在),而在前者中,它不是。它必须是保守的,并假设它不是,因此内存“泄漏”(至少直到fileSize超出范围或更改为分配块之外的值)。

但是C(和C ++)垃圾收集器存在。它们是否有价值是一个不同讨论的问题。

答案 5 :(得分:2)

为C提供precise垃圾收集器是不可能的,因为提供C指针的自由以及C数组的长度是任何人猜测的事实。这意味着不能使用许多复杂的垃圾收集方法。 (复制和压缩垃圾收集器会浮现在脑海中。)

但是,可以实现一个保守的垃圾收集器(boehm),它基本上假定看起来像指针的所有东西都是一个指针。这不是很有效,但它适用于“作品”的适当宽松定义。

答案 6 :(得分:0)

C不是弱类型的,但是这段代码说明了将垃圾收集器构建成语言的难度:

#include <stdio.h>
#include <stdlib.h>

int GetSomeMemory() {
    char* pointerToHeapMemory = malloc(10);
    return (int)pointerToHeapMemory;
}

int main() {
    int memoryAddress = GetSomeMemory();

    /* at this point a garbage collector might decide to clear up the memory that
     * was allocated in GetSomeMemory on the grounds that pointerToHeapMemory 
     * is no longer in scope. But the truth is we still know about that memory and 
     * we're about to use it again... */

    char* anotherPointerToHeapMemory = (char*) memoryAddress;

    sprintf(anotherPointerToHeapMemory, "123456789\0");
    printf("%s\n", anotherPointerToHeapMemory);
}

只要在项目上工作的每个人都同意避免这种事情并使用一组通用的函数来分配和访问内存,就可以完成垃圾收集。例如,this is a C garbage collector implementation