`fopen()`的垃圾收集?

问题描述 投票:2回答:2

Boehm gc只处理内存分配。但如果有人想使用垃圾收集来处理fopen(),那么就不再需要fclose()了。有没有办法在C中这样做?

附:例如,PyPy采用垃圾收集方法来处理打开文件。

最明显的效果是文件(和套接字等)在超出范围时不会立即关闭。对于打开以进行写入的文件,可以将数据保留在其输出缓冲区中一段时间​​,使磁盘上的文件显示为空或截断。

http://doc.pypy.org/en/latest/cpython_differences.html

c garbage-collection fopen pypy boehm-gc
2个回答
3
投票

如果它不明显,那么Boehm GC在C中没有做到。整个库是一堆巨大的未定义行为,有点适用于某些(很多?)现实世界的实现。更先进的,特别是在安全领域,C实现得到的,继续工作的可能性越小。

话虽如此,我认为没有任何理由不能将同样的原则扩展到FILE*句柄。然而,问题在于,它必然是一个保守的GC,其余引用的误报会阻止文件被关闭,并且对进程和文件系统的状态有可见的后果。如果你在正确的地方明确地说fflush,那么它可能只有一半可以接受。

另一方面,使用文件描述符完全没有任何有意义的方法,因为它们是小整数。对于剩余的引用,你基本上总是有误报。


0
投票

TL; DR:是的,但是。更多但不是肯定的。

首先要做的事情。由于标准C库本身必须自动垃圾收集exit()函数中的打开文件句柄(参见下面的标准引号),因此没有必要调用fclose,只要:

  1. 您绝对肯定您的程序最终将通过从main()返回或通过调用exit()终止。
  2. 您不关心文件关闭之前经过了多长时间(使写入该文件的数据可用于其他进程)。
  3. 如果关闭操作失败(可能是因为磁盘故障),则无需通知您。
  4. 您的进程不会打开超过FOPEN_MAX文件,并且不会尝试两次打开同一文件。 (FOPEN_MAX必须至少为8,但包括三个标准流。)

当然,除了非常简单的玩具应用程序之外,这些保证是非常严格的限制,特别是对于为写入而打开的文件。首先,您如何保证主机不会崩溃或断电(排除条件1)?所以大多数程序员认为不关闭所有打开的文件是非常糟糕的风格。

同样,可以设想一个只打开文件进行阅读的应用程序。在这种情况下,从不调用fclose的最严重的问题将是最后一个,同时打开文件限制。五是一个相当小的数字,即使大多数系统有更高的限制,它们几乎都有限制;如果应用程序运行的时间足够长,它将不可避免地打开太多文件。 (条件3也可能是一个问题,虽然并非所有操作系统都强加此限制,并且很少有系统对仅为读取而打开的文件施加限制。)

事实上,这些正是垃圾收集在理论上可以帮助解决的问题。通过一些工作,可以获得垃圾收集器来帮助管理同时打开的文件的数量。但是......如上所述,有许多Buts。这里有几个:

  1. 标准库没有义务使用FILE动态分配malloc对象,或者实际上根本动态分配它们。 (例如,只允许八个打开文件的库可能具有八个FILE结构的内部静态分配数组。)因此垃圾收集器可能永远不会看到存储分配。为了让垃圾收集器移除FILE对象,每个FILE*都需要包装在一个动态分配的代理(一个“句柄”)中,每个接受或返回FILE*指针的接口必须用一个创建一个代理。这不是太多的工作,但有很多接口要包装,包装器的使用基本上依赖于源代码修改;如果某些文件由外部库函数打开,您可能会发现很难引入FILE*代理。
  2. 虽然垃圾收集器可以在删除某些对象之前被告知要做什么(见下文),但是大多数垃圾收集器库没有提供除了内存可用性之外的对象创建限制的接口。垃圾收集器只能解决“太多打开文件”问题,如果它知道允许同时打开多少文件,但它不知道,并且它没有办法告诉它。因此,当这个限制即将被破坏时,您必须安排手动调用垃圾收集器。当然,由于您已经按照第1点将所有调用包装到fopen,您可以通过跟踪打开的文件计数或通过响应fopen()的错误指示将此逻辑添加到包装器中。 (C标准没有指定用于检测此特定错误的可移植机制,但Posix说fopen应该失败并且如果进程有太多文件打开则将errno设置为EMFILE.Posix还定义了ENFILE错误值,用于那里的情况在所有进程中总共打开的文件太多;考虑这两种情况可能是值得的。)
  3. 此外,垃圾收集器没有将垃圾收集限制为单个资源类型的机制。 (在标记清除垃圾收集器(例如BDW收集器)中实现它是非常困难的,因为需要扫描所有使用的内存以查找实时指针。)因此,只要所有文件描述符槽用完,就会触发垃圾收集结果很贵。
  4. 最后,垃圾收集器不保证及时收集垃圾。如果没有资源压力,垃圾收集器可能会长时间处于休眠状态,如果您依靠垃圾收集器关闭文件,这意味着文件可以保持打开状态无限期,即使它们是不再使用。因此,省略fclose()的原始要求列表中的前两个条件继续有效,即使使用垃圾收集器也是如此。

所以。是的,但是,但是,但是,但是。以下是Boehm GC文档推荐(缩写):

  • 必须立即执行的操作......应该通过代码中的显式调用来处理。
  • 只要方便,应明确管理稀缺的系统资源。仅将[垃圾收集]用作显式难以处理的情况的备份机制。
  • 如果使用[垃圾收集器]管理稀缺资源,则该资源的分配例程(例如,打开文件句柄)应该强制进行垃圾收集(如果这不足够则为两个),如果它发现自己缺少资源。
  • 如果管理极其稀缺的资源(例如,在具有20个打开文件的限制的系统上的文件描述符),则可能需要引入描述符高速缓存方案来隐藏资源限制。

现在,假设你已经阅读了所有内容,而你仍然想要这样做。它实际上非常简单。如上所述,您需要定义一个包含FILE*的代理对象或句柄。 (如果你使用像open()这样使用文件描述符的Posix接口 - 小整数 - 而不是FILE结构,那么句柄就会保存fd。这显然是一种不同的对象类型,但机制是相同的。)

fopen()(或open(),或任何其他返回打开FILE*s或文件的调用)的包装中,你动态分配一个句柄,然后(在Boehm GC的情况下)调用GC_register_finalizer告诉垃圾收集器什么函数在资源即将被删除时调用。几乎所有的GC图书馆都有这样的设施;在他们的文档中搜索finalizer。这是documentation for the Boehm collector,我从中提取了上面的警告列表。

当您打开公开呼叫时,请注意避免竞争条件。建议的做法如下:

  1. 动态分配句柄。
  2. 将其内容初始化为sentinel值(例如-1或NULL),表示尚未将句柄分配给打开的文件。
  3. 注册句柄的终结器。终结器函数应该在尝试调用fclose()之前检查sentinel值,因此在此时注册句柄是正常的。
  4. 打开文件(或其他此类资源)。
  5. 如果打开成功,请重置句柄以使用从打开返回的句柄。如果失败与资源耗尽有关,则触发手动垃圾收集并根据需要重复。 (小心限制单次打开包装器的次数。有时你需要做两次,但连续三次失败可能表示其他一些问题。)
  6. 如果打开最终成功,则返回句柄。否则,可选择取消注册终结器(如果GC库允许)并返回错误指示。

Obligatory C standard quotes

  1. main()返回与调用exit()相同 §5.1.2.2.3(程序终止):(仅适用于托管实现) 如果main函数的返回类型是与int兼容的类型,则从初始调用到main函数的返回等效于使用main函数返回的值作为其参数调用exit函数;到达终止}函数的main返回值0。
  2. 调用exit()会刷新所有文件缓冲区并关闭所有打开的文件 §7.22.4.4(退出功能): 接下来,刷新所有带有未写入缓冲数据的开放流,关闭所有打开的流,并删除由tmpfile函数创建的所有文件...
© www.soinside.com 2019 - 2024. All rights reserved.