当使用像C++
这样没有任何自动垃圾收集器的语言时,我明白你必须使用析构函数来清理你的对象。但是,与非OOP语言相关的“清理”概念如何?例如,在C
清理结构和内置类型的标准方法是什么?
举一个具体的例子,在编写一个长期运行的C
进程时,相关的清理概念是什么?
答案 0 :(得分:3)
在C中,要解除分配动态分配的资源,您必须调用free
。
一个常见的解决方案是编写模仿析构函数的函数,因为它们通过在需要的地方调用free
来对特定结构进行清理。
但值得注意的是,这些清理函数不会像析构函数那样自动调用,而忘记调用清理函数可能会导致内存泄漏。
答案 1 :(得分:3)
根据垃圾收集的wikipedia条目
在计算机科学中,垃圾收集(GC)是自动内存管理的一种形式。
和
垃圾收集通常被描述为手动内存管理的对立面,这需要程序员指定哪些对象要解除分配并返回到内存系统。
在C
语言中,没有自动内存管理,因此我们必须执行手动内存管理。无论程序(或程序员)在何时(动态地,在运行时)分配内存,程序(或程序员)都需要使用free()
函数释放内存明确性。
未能释放分配的内存将导致memory leak。
答案 2 :(得分:1)
与使用垃圾收集语言没什么不同,当您在GC系统中编写类时,您必须经常编写finaliser或IDispose方法。这可确保在GC根据该类清除任何对象时,释放您在类中分配的任何资源。因此,DB包装器类可能会在其构造函数中打开数据库连接句柄,您将编写一个终结器以便稍后关闭该句柄。
显然 - 您已经编写了打开句柄的代码,以及更多关闭它的代码。这与其他语言(例如C)的原理相同。只有一个区别:如果您的类只管理内存,则可以忽略对象中使用的内存的创建和销毁。在C中,您必须分配和释放您使用的内存。在GC语言中,为您处理内存。
但是,您仍然需要管理非内存资源。用C语言编写时,只需将内存视为另一种资源。
答案 3 :(得分:1)
通过使用句柄,可以使用非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%)。
答案 4 :(得分:0)
请注意,面向对象是程序设计方法,编程语言可能有也可能没有内置的各种便利功能。特定语言如何处理动态内存分配与OO并不真正相关,尽管在编写OO程序时必须考虑它。
由于C没有语言特性构造函数/析构函数,因此面向对象的C程序必须手动调用“init函数”和“析构函数”。如果使用C语言功能不完整类型(有时称为“opaque类型”)正确实现这些功能,那么就OO设计而言,您可以实现完全相同的功能。但不方便的是,你必须手动调用这些函数。
正如在C ++中编写代码时必须考虑删除可能更不方便,相比之下,例如Java,你可以分配,让其他人(垃圾收集器)清理你的混乱,可能在花费较慢的程序。