2

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

PS 例如,PyPy 采用垃圾回收的方式来处理打开文件。

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

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

4

2 回答 2

3

如果不是很明显, Boehm GC 在 C 中做的任何事情都是不可能的。整个库是一大堆未定义的行为,碰巧在某些(很多?)现实世界的实现中起作用。C 实现越先进,尤其是在安全领域,C 实现就越不可能继续工作。

话虽如此,我看不出有任何理由不能将相同的原则扩展到FILE*句柄。然而,问题在于它必然是一个保守的 GC,剩余引用的误报会阻止文件被关闭,这会对进程和文件系统的状态产生明显的影响。但是,如果您明确地fflush在正确的位置,它可能只是半损坏是可以接受的。

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

于 2019-02-21T00:59:02.687 回答
0

TL;DR:是的,但是。不仅如此。

第一件事。由于标准 C 库本身必须在函数中自动对打开的文件句柄进行垃圾收集exit()请参阅下面的标准引号),所以只要:fclose

  1. 您绝对可以肯定您的程序最终会通过从返回main()或调用来终止exit()

  2. 您不在乎在关闭文件之前经过了多少时间(使写入文件的数据可供其他进程使用)。

  3. 如果关闭操作失败(可能是由于磁盘故障),则不需要通知您。

  4. 您的进程不会打开多个FOPEN_MAX文件,并且不会尝试打开同一个文件两次。(FOPEN_MAX必须至少有 8 个,但这包括三个标准流。)

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

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

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

  1. 标准库没有义务使用 动态分配FILE对象malloc,或者实际上根本没有动态分配它们的义务。(例如,一个只允许打开八个文件的库可能有一个内部静态分配的八个FILE结构数组。)因此垃圾收集器可能永远看不到存储分配。为了让垃圾收集器参与到对象的删除中FILE,每个对象都FILE*需要包装在一个动态分配的代理(“句柄”)中,并且每个接受或返回FILE*指针的接口都必须用一个创建代理的接口包装。这不是太多的工作,但是有很多接口要包装,并且包装器的使用基本上依赖于源代码修改;你可能会觉得很难介绍FILE*如果某些文件由外部库函数打开,则为代理。

  2. 尽管垃圾收集器可以在删除某些对象之前被告知要做什么(见下文),但大多数垃圾收集器库没有提供除内存可用性之外的对象创建限制的接口。垃圾收集器只有知道允许同时打开多少个文件,才能解决“打开文件过多”的问题,但它不知道,也没有办法让你告诉它。因此,您必须安排在即将突破此限制时手动调用垃圾收集器。当然,由于您已经包装了对 的所有调用fopen,根据第 1 点,您可以将此逻辑添加到包装器中,方法是跟踪打开的文件计数,或者对来自的错误指示做出反应fopen(). (C 标准没有指定用于检测此特定错误的可移植机制,但 Posix 表示如果进程打开了太多文件,则fopen应该失败并设置errno为。Posix 还为文件太多的情况定义了错误值在所有进程中完全打开;可能值得考虑这两种情况。)EMFILEENFILE

  3. 此外,垃圾收集器没有将垃圾收集限制为单一资源类型的机制。(在标记清除垃圾收集器(例如 BDW 收集器)中实现这一点非常困难,因为需要扫描所有使用的内存以找到活动指针。)因此,只要所有文件描述符插槽都用完就触发垃圾收集可以结果是相当昂贵。

  4. 最后,垃圾收集器不保证垃圾会被及时收集。如果没有资源压力,垃圾收集器可能会长时间处于休眠状态,如果您依赖垃圾收集器关闭文件,这意味着文件可以无限期保持打开状态,即使它们是不再使用。因此,即使使用垃圾收集器,原始要求列表中的前两个条件fclose()仍然有效。

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

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

现在,假设您已经阅读了所有这些内容,并且仍然想这样做。其实很简单。如上所述,您需要定义一个代理对象或句柄,其中包含一个FILE*. (如果您使用的是 Posix 接口,比如open()使用文件描述符——小整数——而不是FILE结构,则句柄保存 fd。显然,这是一种不同的对象类型,但机制是相同的。)

fopen()在您的(或open(),或任何其他返回 open 或文件的调用)的包装器中FILE*,您动态分配一个句柄,然后(在 Boehm GC 的情况下)调用GC_register_finalizer以告诉垃圾收集器在资源即将被删除。几乎所有的 GC 库都有一些这样的功能。finalizer在他们的文档中搜索。这是Boehm 收集器的文档,我从中提取了上面的警告列表。

当你包装开放电话时,请注意避免竞争条件。推荐的做法如下:

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

强制性 C 标准报价

  1. 从返回main()与调用相同exit()

    §5.1.2.2.3(程序终止):(仅适用于托管实现)

    1. 如果main函数的返回类型是与 兼容的类型int,则从函数的初始调用返回main等效于以函数返回的值main作为参数调用退出函数;到达}终止main函数的返回值 0。
  2. 调用exit()刷新所有文件缓冲区并关闭所有打开的文件

    §7.22.4.4(退出功能):

    1. 接下来,刷新所有打开的带有未写入缓冲数据的流,关闭所有打开的流,并tmpfile删除该函数创建的所有文件……</li>
于 2019-02-21T20:20:09.737 回答