垃圾收集的概念与非 OOP 语言有何关系

How is the concept of garbage collection related to non OOP languages

当使用像 C++ 这样没有任何自动垃圾收集器的语言时,我知道您必须使用析构函数来清理对象。但是 "cleaning up" 的概念与非 OOP 语言有何关系。例如,在 C 中清理结构和内置类型的标准方法是什么?

举个具体的例子,写一个长运行C进程时,清理的相关概念是什么?

在 C 中,要释放动态分配的资源,您必须调用 free

一个常见的解决方案是编写模仿析构函数的函数,因为它们通过在需要时调用 free 来对特定结构进行清理。

但值得注意的是,这些清理函数不会像析构函数那样自动调用,忘记调用清理函数可能会导致内存泄漏。

根据垃圾收集的 wikipedia 条目

In computer science, garbage collection (GC) is a form of automatic memory management.

Garbage collection is often portrayed as the opposite of manual memory management, which requires the programmer to specify which objects to deallocate and return to the memory system.

C语言中,没有自动内存管理所以我们必须手动内存管理。无论程序(或程序员)分配(动态地,在运行时)内存是什么,程序(或程序员)都需要 释放 显式内存,使用 free() 函数。

未能释放分配的内存将导致memory leak

请注意,面向对象是一种程序设计方法,编程语言可能内置也可能不内置各种方便的功能。特定语言如何处理动态内存分配与 OO 并没有真正的关系,尽管在编写 OO 程序时必须考虑到这一点。

由于 C 没有语言特性 constructor/destructor,您的面向对象的 C 程序必须手动调用 "init function" 和 "destruct function"。如果您通过使用 C 语言功能 不完整类型 (有时称为 "opaque type")正确实现这些,就 OO 设计而言,您可以实现完全相同的结果。但不方便的是,您必须手动调用这些函数。

正如在用 C++ 编写代码时必须考虑删除可能更不方便,与例如 Java 相比,您可以分配空间并让其他人(垃圾收集器)清理你的混乱,可能以较慢的程序为代价。

它与使用垃圾收集语言没有太大区别,当您在 GC 系统中编写 class 时,您必须经常编写终结器或 IDispose 方法。这确保当 GC 清理基于 class 的任何对象时,您在 class 中分配的任何资源都会被释放。因此,数据库包装器 class 可能会在其构造函数中打开一个数据库连接句柄,稍后您将编写一个终结器来关闭该句柄。

显然 - 您已经编写了打开句柄的代码,以及更多关闭句柄的代码。这对于其他语言也是一样的原理,比如C。只有1个区别:如果你的class只管理内存,你可以忽略对象中使用的内存的创建和销毁。在 C 中,您必须分配和释放您使用的内存。在 GC 语言中,内存是为你处理的。

但是您仍然需要管理非内存资源。当用 C 等语言编写时,只需将内存视为另一种资源。

可以使用句柄在非 GC 语言中建立垃圾收集系统。如果有人想为必须处理大量可变长度字符串的嵌入式系统编写 C 程序——其中许多是重复的——例如64K RAM,可以写一个 API 其成员包括以下内容:

// With 64K RAM, won't need over 64,000 string handles
typedef uint16_t st_handle;  
// Create new handle that initially identifies an empty string
st_handle st_create();
// Release handle
void st_release(st_handle st);
// Create a string object with a copy of the specified content and make
// the given handle identify it
void st_copy_content(st_handle st, const char *src, int src_length);
// Create a string object which holds a reference to a string held in memory
// that will never change (e.g. ROM), and make handle identify it
void st_make_reference(st_handle st, char *src, int src_length);
// Get length of string object associated with handle
int st_length(st_handle st);
// Copy portion of string object to character array or buffer
void st_get_range(st_handle st, int st_index, char *dest, int dest_length);
// Make one handle identify the same string as another
void st_assign(st_handle dest, st_handle src);
// Report how much memory is immediately available or could be made available,
// possibly performing a garbage-collection first based upon "mode" and
// "requirement".
int st_free_space(int mode, int requirement);

在处理字符串类型变量之前,代码需要创建一个句柄并将其存储在该变量中;处理完变量后,代码需要释放该句柄。代码可以将句柄复制到其他变量(例如,用于传递给函数),但每个句柄必须在最后一次使用后释放,并且在此之后既不使用也不冗余释放。

尽管需要手动创建和释放 handles,但使用大量代码比使用手动分配的 strings[=23= 更有效].由于每个句柄将标识一个固定大小的对象(它又将标识一个可变长度的字符串),因此可以通过从池中返回第一个可用句柄来满足任何 "allocate-handle" 请求。如果字符串是不可变的,那么允许多个句柄识别同一个字符串不仅可以使字符串分配(从一个句柄到另一个句柄)比字符串复制更快——它还可以消除在 RAM 中保存字符串数据副本的需要。虽然上面没有显示,但合适的库还可以包括连接、提取子字符串等方法。

需要做库未提供的事情的代码可能需要在使用它们之前将数据从它们复制到适当大小的缓冲区,并在完成后从这些缓冲区创建新的字符串对象,但大多数程序不会一次 "working with" 很多字符串——相反,它们将处理数组中的一个字符串,然后处理另一个字符串,等等。因此有一个池来管理不是 [=27= 的字符串] 在任何给定时间,只占用字符串本身所需的内存,可以大大提高效率。

由于字符串池会知道对每个字符串的每个引用的下落,因此可以自由地重新排列内存中的字符串。如果代码创建了许多短字符串,释放其中一半,然后尝试创建更长的字符串,那么传统的字符串分配器会遇到麻烦,而基于句柄的分配器则不会有任何困难。如果需要,它可以将仍然存在引用的所有字符串重新定位到池的顶部,从而为中间的较大字符串腾出空间。此外,句柄池分配器可以非常节俭。虽然 ARM 通常会为每个指向分配的内存块的指针使用 4 个字节,并且 malloc 每个对象需要额外的 4 个字节来跟踪堆使用情况,但 64K 或更小的垃圾收集池只需要 2+2。单字符字符串可以直接存储在句柄中,而长度为 2-63 的字符串对象只需要一个字节的开销(更长的字符串可能需要更多的开销,具体取决于长度,但在任何情况下都不到 2%)。