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

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

本文关键字:何关系 语言 关系 OOP      更新时间:2023-10-16

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

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

根据垃圾回收的维基百科条目

在计算机科学中,垃圾回收(GC)是自动内存管理的一种形式。

垃圾回收通常被描述为与手动内存管理相反,手动内存管理要求程序员指定要释放并返回到内存系统的对象。

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

未能释放分配的内存将导致内存泄漏。

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

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

但是,值得注意的是,这些清理函数

不会像析构函数那样自动调用,忘记调用清理函数可能会导致内存泄漏。

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

显然 - 你已经编写了打开句柄的代码,以及更多关闭句柄的代码。对于其他语言(如 C),这是相同的原则。只有 1 个区别:如果您的类只管理内存,则可以忽略对象中使用的内存的创建和销毁。在 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);

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

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

需要执行库未提供的操作的代码可能需要在使用它们之前将数据从它们复制到适当大小的缓冲区中,并在完成后从这些缓冲区创建新的字符串对象,但大多数程序不会一次"处理"很多字符串 - 相反,它们将处理数组中的一个字符串, 然后与另一个人一起工作,等等,所以有一个池来管理在任何给定时间没有被"处理"的字符串,并且只占用字符串本身需要的尽可能多的内存,可以大大提高效率。

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

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

由于 C 没有语言特性构造函数/析构函数,因此面向对象的 C 程序必须手动调用"init 函数"和"析构函数"。如果使用 C 语言功能不完整类型(有时称为"不透明类型")正确实现这些内容,则就 OO 设计而言,可以实现完全相同的事情。但不方便的是,您必须手动执行这些函数的每次调用。

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