Boehm gc只处理内存分配。但如果有人想使用垃圾收集来处理fopen()
,那么就不再需要fclose()
了。有没有办法在C中这样做?
附:例如,PyPy采用垃圾收集方法来处理打开文件。
最明显的效果是文件(和套接字等)在超出范围时不会立即关闭。对于打开以进行写入的文件,可以将数据保留在其输出缓冲区中一段时间,使磁盘上的文件显示为空或截断。
如果它不明显,那么Boehm GC在C中没有做到。整个库是一堆巨大的未定义行为,有点适用于某些(很多?)现实世界的实现。更先进的,特别是在安全领域,C实现得到的,继续工作的可能性越小。
话虽如此,我认为没有任何理由不能将同样的原则扩展到FILE*
句柄。然而,问题在于,它必然是一个保守的GC,其余引用的误报会阻止文件被关闭,并且对进程和文件系统的状态有可见的后果。如果你在正确的地方明确地说fflush
,那么它可能只有一半可以接受。
另一方面,使用文件描述符完全没有任何有意义的方法,因为它们是小整数。对于剩余的引用,你基本上总是有误报。
TL; DR:是的,但是。更多但不是肯定的。
首先要做的事情。由于标准C库本身必须自动垃圾收集exit()
函数中的打开文件句柄(参见下面的标准引号),因此没有必要调用fclose
,只要:
main()
返回或通过调用exit()
终止。FOPEN_MAX
文件,并且不会尝试两次打开同一文件。 (FOPEN_MAX
必须至少为8,但包括三个标准流。)当然,除了非常简单的玩具应用程序之外,这些保证是非常严格的限制,特别是对于为写入而打开的文件。首先,您如何保证主机不会崩溃或断电(排除条件1)?所以大多数程序员认为不关闭所有打开的文件是非常糟糕的风格。
同样,可以设想一个只打开文件进行阅读的应用程序。在这种情况下,从不调用fclose
的最严重的问题将是最后一个,同时打开文件限制。五是一个相当小的数字,即使大多数系统有更高的限制,它们几乎都有限制;如果应用程序运行的时间足够长,它将不可避免地打开太多文件。 (条件3也可能是一个问题,虽然并非所有操作系统都强加此限制,并且很少有系统对仅为读取而打开的文件施加限制。)
事实上,这些正是垃圾收集在理论上可以帮助解决的问题。通过一些工作,可以获得垃圾收集器来帮助管理同时打开的文件的数量。但是......如上所述,有许多Buts。这里有几个:
FILE
动态分配malloc
对象,或者实际上根本动态分配它们。 (例如,只允许八个打开文件的库可能具有八个FILE
结构的内部静态分配数组。)因此垃圾收集器可能永远不会看到存储分配。为了让垃圾收集器移除FILE
对象,每个FILE*
都需要包装在一个动态分配的代理(一个“句柄”)中,每个接受或返回FILE*
指针的接口必须用一个创建一个代理。这不是太多的工作,但有很多接口要包装,包装器的使用基本上依赖于源代码修改;如果某些文件由外部库函数打开,您可能会发现很难引入FILE*
代理。fopen
,您可以通过跟踪打开的文件计数或通过响应fopen()
的错误指示将此逻辑添加到包装器中。 (C标准没有指定用于检测此特定错误的可移植机制,但Posix说fopen
应该失败并且如果进程有太多文件打开则将errno
设置为EMFILE
.Posix还定义了ENFILE
错误值,用于那里的情况在所有进程中总共打开的文件太多;考虑这两种情况可能是值得的。)fclose()
的原始要求列表中的前两个条件继续有效,即使使用垃圾收集器也是如此。所以。是的,但是,但是,但是,但是。以下是Boehm GC文档推荐(缩写):
现在,假设你已经阅读了所有内容,而你仍然想要这样做。它实际上非常简单。如上所述,您需要定义一个包含FILE*
的代理对象或句柄。 (如果你使用像open()
这样使用文件描述符的Posix接口 - 小整数 - 而不是FILE
结构,那么句柄就会保存fd。这显然是一种不同的对象类型,但机制是相同的。)
在fopen()
(或open()
,或任何其他返回打开FILE*
s或文件的调用)的包装中,你动态分配一个句柄,然后(在Boehm GC的情况下)调用GC_register_finalizer
告诉垃圾收集器什么函数在资源即将被删除时调用。几乎所有的GC图书馆都有这样的设施;在他们的文档中搜索finalizer
。这是documentation for the Boehm collector,我从中提取了上面的警告列表。
当您打开公开呼叫时,请注意避免竞争条件。建议的做法如下:
fclose()
之前检查sentinel值,因此在此时注册句柄是正常的。main()
返回与调用exit()
相同
§5.1.2.2.3(程序终止):(仅适用于托管实现)
如果main
函数的返回类型是与int
兼容的类型,则从初始调用到main
函数的返回等效于使用main
函数返回的值作为其参数调用exit函数;到达终止}
函数的main
返回值0。exit()
会刷新所有文件缓冲区并关闭所有打开的文件
§7.22.4.4(退出功能):
接下来,刷新所有带有未写入缓冲数据的开放流,关闭所有打开的流,并删除由tmpfile
函数创建的所有文件...