垃圾回收为什么没有为C语言设计?是否有特定原因?

27

我听说自动回收垃圾对于 C 语言来说并不是最佳选择,这是真的吗?

为什么 C 语言没有实现垃圾回收机制呢?


13
还剩下什么吗? - Travis Christian
1
由于几乎所有的东西都是基于C构建的,如果为C实现了GC,那么只有C将存在 :) - Matt Joiner
13个回答

58

不要听那些声称“C语言过时了,所以没有垃圾回收机制”的人。GC存在一些无法克服的基本问题,使其与C语言不兼容。

最大的问题是准确的垃圾回收需要扫描内存并识别任何遇到的指针。一些高级语言将整数限制为不使用所有可用位,以便高位可以用于区分对象引用和整数。这些语言可能会将字符串(可能包含任意字节序列)存储在一个特殊的字符串区域中,以便它们不能与指针混淆,一切都很好。然而,C语言实现无法做到这一点,因为字节、较大的整数、指针和其他所有内容都可以存储在结构、联合或通过malloc返回的块的一部分中。

如果你抛弃准确性要求,并决定接受一些对象永远不被释放的情况,因为程序中的一些非指针数据具有与这些对象地址相同的位模式怎么办?现在假设你的程序从外部世界(网络/文件等)接收数据。只要我能够猜测足够多的指针并在我向你的程序提供的字符串中模拟它们,我就能让你的程序泄漏任意数量的内存,并最终耗尽内存。如果应用De Bruijn序列,这将变得更加容易。

除此之外,垃圾回收就是一件非常慢的事情。您可以找到数百个喜欢声称相反的学者,但这并不会改变现实。GC的性能问题可以分为3大类:

  • 不可预测性
  • 缓存污染
  • 遍历所有内存所需的时间

那些声称现在GC很快的人只是将其与错误的东西进行比较:分配和释放成千上万或数百万个对象的写得很糟糕的C和C++程序。是的,这些也会很慢,但至少以一种可以测量和修复的可预测方式缓慢。一个编写良好的C程序将花费如此少的时间在malloc/free中,以至于开销甚至无法测量。


20
引用计数和垃圾回收不是同一回事。引用计数简洁优雅,但无法自动释放循环链表等循环引用的对象,而且在多线程环境中,不同的线程可能同时尝试增加或减少引用计数,使其变得非常难以高效处理。 - R.. GitHub STOP HELPING ICE
6
我能够想到至少两个适用于 C 语言的垃圾收集器。因此,“GC 存在无法克服的基本问题,使其与 C 不兼容”是错误的说法。 - JeremyP
17
他们受到我在帖子中解释的缺陷的影响。事实上,某人能够编写错误的、易受攻击的实现,并不否认我所说的无法编写正确、强大的实现的论点。 - R.. GitHub STOP HELPING ICE
8
@JeremyP:这是不可能的。这就是我的回答的本质。一个可以被攻击者控制并泄漏内存的程序远非强壮,更谈不上“足够强壮”了。 - R.. GitHub STOP HELPING ICE
2
“你可以找到数百名学者声称 GC 不快,但这并不会改变现实。”,“那些声称现在 GC 很快的人只是将其与编写不良的 C 进行比较而已。”-- “你能提供一些参考资料吗?我读过很多关于 GC 性能的论文,但没有一篇像这样。”如果我们因为认为它不符合“现实”而削弱研究,那么我们是否应该削弱 所有 计算机科学研究呢? - Ssswift
显示剩余10条评论

26

垃圾回收已经被实现在C语言中(例如Boehm-Demers-Weiser collector)。由于当时硬件和系统的限制,C语言最初并没有被指定为包含GC的语言。

编辑(回答一些其他帖子中提出的指责):

为了使保守 GC 有明确定义,你基本上只需要对语言进行一个更改:声明任何使指针暂时“不可见”的行为都会导致未定义的行为。例如,在当前的 C 中,你可以将一个指针写入文件,覆盖内存中的指针,稍后将其读回,并且(假设先前是有效的)仍然访问其指向的数据。GC 不一定会“意识到”该指针的存在,因此它可能会将内存视为不再可访问,因此开放给收集,因此稍后的间接引用将无法“工作”。
就垃圾回收非确定性而言:有实时收集器是绝对确定性的,可以用于硬实时系统。还有用于手动管理的确定性堆管理器,但大多数手动管理器不是确定性的。
至于垃圾回收速度慢和/或缓存抖动:技术上,这是有点真实的,但这只是一个技术问题。虽然已知设计(例如,分代清理)(至少大部分)避免了这些问题,但可以争论它们并不完全是垃圾回收(尽管它们对程序员做了几乎相同的事情)。
至于 GC 在未知或意外的时间运行:这不一定比手动管理的内存更真实或更少。你可以让 GC 在运行时(至少有点)不可预测的单独线程中运行。对于手动内存管理的合并空闲块也是如此。尝试分配内存可能会触发收集循环,导致某些分配比其他分配慢得多;使用惰性合并自由块的手动管理器也是如此。
奇怪的是,GC 与 C ++ 相比,要 不 兼容得多。大多数 C++ 都依赖于确定性地调用析构函数,但是使用垃圾回收后情况就不再如此了。这会破坏大量代码-而且编写得越好,通常引起的问题就越大。
同样,C ++ 要求std::less<T>在指针完全独立的对象上提供有意义的(更重要的是一致的)结果。使用复制收集器/清道夫需要额外的工作才能满足此要求(但我相信这是可能的)。处理(例如)某人散列地址并期望一致的结果仍然更加困难。这通常是一个糟糕的主意,但仍然是可能的,并且应产生一致的结果。

2
@R:在我看来,你的回答并没有指出真正无法弥补的缺陷。相反,它指出了一个大多数情况下都是理论问题,即使在非常罕见的情况下可能会变得显著,也很容易解决。这并不意味着GC完美无缺,但你的回答似乎也没有特别准确地展示其优点或缺点。 - Jerry Coffin
1
@Jerry:一个易受攻击的DOS漏洞并不是“大多数都是理论上的”。 - R.. GitHub STOP HELPING ICE
1
德布鲁因序列使猜测变得更容易,而其他(但可能是互相排斥的)启发式方法也应该让您猜测出许多有效的指针。我毫不怀疑这样的攻击对于Web浏览器、聊天客户端等非常可行。至于让你说话,我道歉了。 - R.. GitHub STOP HELPING ICE
1
我喜欢“但大多数手动管理者并不是确定性的”。人们往往会忘记这一点。 - Prof. Falken
1
我可能在这里散布恐慌,但是在Rails中,使用一种具有真正GC的语言,由于内存问题,每分钟重新启动几次被认为是完全正常的。因此,即使是真正的GC也不是万能药,适当的宽容会带来很大的帮助。这是来自一个使用libgc的人的建议。 - Prof. Falken
显示剩余3条评论

18

C语言是在20世纪70年代早期发明的,用于编写操作系统和其他低级别的东西。当时垃圾收集器已经存在(例如早期版本的Smalltalk),但我怀疑它们能否胜任在这样一个轻量级环境中运行的任务,而且还需要处理非常低级别的缓冲区和指针的所有复杂性。


1
这是个不错的回答,但是wllmsaccnt的第二段更符合我要找的内容。 - Raven Dreamer
4
+1 针对指出在 C 语言创建时 GC 已经“可用”的观点。许多人似乎忘记了所有这些“现代化的花哨功能”已经存在(以某种形式)数十年了。 - user166390
@Raven Dreamer:这可能更符合您的要求,但不幸的是,它几乎完全错误。@winwaed的答案更加准确。 - Jerry Coffin
我的观点是,C语言之所以是C语言,是因为它就是被设计成这样的。如果它被设计成带有垃圾回收器,那么它可能对早期系统程序员来说并不会像现在这样有用,并且也无法满足早期程序员的需求。它没有那些花里胡哨的功能,不是因为这些功能当时不可用,而是因为当时的系统速度很慢,垃圾回收并不是一个优先考虑的问题。 - keithwill

16

很抱歉,认为C语言没有垃圾回收是因为它太老或是为不同的硬件设计而来,这种说法有点可笑。

问为什么C语言没有垃圾回收而其他语言有,则有点像问“为什么锤子有钝头而斧头有尖头”?嗯,语言就是工具,不同的工具用于构建具有不同要求的不同程序。一些程序(例如设备驱动程序或操作系统内核)具有使其几乎不可能实现垃圾回收却仍满足需求的要求,因此需要一种像C这样的语言,所以C被创建出来了。

就像每个工具一样,它之所以是它的样子,是因为它所解决的问题迫使它成为那样。那些认为C是由于70年代创建或由于旧硬件而被强迫的人让它看起来完全是环境所造成的。如果这是真的,C语言就会随着70年代的结束而消失,但它仍然是一种流行的语言。

此外,垃圾回收非常难以干净地实现,而且需要某种运行时系统,但C语言是用于裸机运行的。


一些程序,比如设备驱动程序或操作系统内核,有着几乎不可能同时满足垃圾回收和它们自身需求的要求,因此需要像C这样的语言,于是C应运而生。-- 你能给出一个具体的例子吗? - Raven Dreamer
6
我比起回答的其余部分更喜欢最后一个段落。 - Prof. Falken

11

C语言是一种非常老的语言,缺乏现代语言的许多花哨功能。现在要添加垃圾收集功能将需要对该语言进行重大改进。通常情况下,任何想要对C语言进行这么多改变的人都会更愿意创建自己的语言。

向语言中添加自动垃圾收集功能通常会降低性能,或者导致垃圾收集在不可预见的时间发生。向C语言添加垃圾收集将导致其失去其中一个比较优势,即可用于需要实时或接近实时响应时间的系统级编程。


16
我会冒险说,虽然我总体上同意你的回答,但实时并不存在。所有“实时”程序只是以所需的速度运行 - 你可以拥有需要几小时或几天才能响应的“实时”系统。我认为你要找的概念是“确定性”。添加垃圾收集器将使C语言的确定性变差,从而更难验证它是否能够在用户对当天的预期时间内实现所需的响应。 - Peter M
8
很不幸,这其中大部分都是胡说八道。为了实现保守式垃圾回收,只需要进行一些(主要是微不足道的)改变即可。要实现“精确”垃圾回收,需要进行更多改动,但仍然远未达到“彻底重新设计”的程度。有些实时垃圾回收器提供了比大多数malloc/calloc/realloc/free实现所使用的堆管理器更加确定性的时间控制。 - Jerry Coffin
4
抱歉直言,但你似乎不知道自己在说什么。尽管不是全新的,但ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps可以让人们了解真正的分配器是如何工作的。 - Jerry Coffin
1
反垃圾回收(Pro-GC)宣传不是我获取真实分配器工作信息的来源。我的经典来源是“dlmalloc”,但我承认已经很久没有仔细阅读过了。 - R.. GitHub STOP HELPING ICE
2
一般来说,自动内存管理在普通程序员手中的表现实际上比手动内存管理要好。理论上讲,手工制作完美的内存管理是可能的,但实际上这是不会发生的。此外,事实上,存在实时(以确定性为意义)能力的垃圾收集器。例如,Baker在1978年概述了其中之一,并在1992年对其进行了改进。请注意,这是改进版本已经超过十年以上的时间了。基本上,忽略你认为自己知道的关于GC的一切。你可能会非常尴尬地错了。 - JUST MY correct OPINION
显示剩余2条评论

7
作为一种语言,C语言被有意地设计得非常小。它几乎没有内置操作或特性,而大多数都反映了CPU中的基本指令。它经常被称为“可移植汇编语言”。
这使得它非常适合编写可移植的程序来切换电话呼叫,运行在非常小的计算机上,内存非常有限,这也是贝尔实验室在70年代初首次研究C和Unix时追求的目标之一。
这也使C非常适合编写操作系统内核、设备驱动程序等。这样的可执行文件不能依赖于丰富、复杂的运行时环境,如垃圾回收 - 或仅仅像malloc()这样的动态内存分配。这是因为它们实际上构成了这些环境的基础,所以你会遇到“拉起自己的靴带”的问题。
人们已经为C编写了垃圾收集内存分配库。但是库不是C语言本身的一部分,而且很难想象这样复杂的东西会被接受为标准的C库的一部分。C程序员对标准的更改非常保守。

5

C是一种非常底层的语言。它是一种你可能会选择用来编写高级语言的语言,例如垃圾回收等。它很小而简单,恰好做你所要求的事情。

C++在C的基础上构建,并添加了更复杂/自动的内存管理(例如在对象超出范围时调用析构函数)。你可能会想为什么C++没有垃圾回收,如果是这样,请看看Stroustrup的解释:简而言之,人们希望以更直接的方式完成任务,并且真正需要它的人可以使用库(也可以在C中使用)。


5

我喜欢JWZ对C语言的看法。 :-)

C语言是“认为自己是一种语言的PDP-11汇编语言”。

因此,如果您考虑创建一个可移植的汇编程序,那么不带垃圾回收的C语言看起来完全正确,原因很简单,CPU没有“垃圾回收”指令。

(虽然到目前为止,大多数其他CPU指令都可以很好地使用C语言捕获,但某些指令不行,例如C语言对SIMD操作的表达能力不足。)

* 是的,我知道有些人会找到反例来证明我错了。但通常情况下...


当然,作为一个认为自己是一门语言的PDP-11汇编语言,是一件好事。 :-) - Prof. Falken

4

Objective_C是在ANSI C的基础上添加了大约12个关键字和一个名为id的类型。它具有垃圾回收功能,使用引用计数。


2
引用计数与垃圾收集完全不同,不受GC的任何严重缺陷的影响。不幸的是,学术界更在意能够编写循环链表… - R.. GitHub STOP HELPING ICE
2
它也具备真正的垃圾回收功能。 - JeremyP

3

C语言的优点(也是缺点)在于它小巧、简单、轻量级,非常适合嵌入式和系统应用。使用GC会增加开销,使其不再小巧简单。对于C语言来说,使用GC并不合适。问题就是这么简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接