【问题标题】:What happens if I don't call fclose() in a C program?如果我不在 C 程序中调用 fclose() 会发生什么?
【发布时间】:2011-12-31 19:52:06
【问题描述】:

首先,我知道用 fopen() 打开一个文件而不关闭它是非常不负责任的,而且是糟糕的形式。这只是纯粹的好奇,所以请幽默:)

我知道如果一个 C 程序打开一堆文件并且从不关闭其中任何一个,最终 fopen() 将开始失败。是否有任何其他副作用可能导致代码本身之外的问题?例如,如果我有一个程序打开一个文件,然后在没有关闭它的情况下退出,这会对运行该程序的人造成问题吗?这样的程序会泄漏任何东西(内存、文件句柄)吗?程序完成后再次访问该文件是否会出现问题?如果程序连续运行多次会怎样?

【问题讨论】:

  • 不需要所有的免责声明。在不调用fclose 的情况下退出并没有什么可怕的,至少不比使用goto 更糟糕。这一切都取决于情况。

标签: c file-io fopen fclose


【解决方案1】:

只要您的程序正在运行,如果您一直打开文件而不关闭它们,最有可能的结果是您的进程可用的文件描述符/句柄将用完,并且尝试打开更多文件最终会失败。在 Windows 上,这也可以防止其他进程打开或删除您打开的文件,因为默认情况下,文件以独占共享模式打开,从而阻止其他进程打开它们。

一旦您的程序退出,操作系统将在您之后进行清理。它会在终止进程时关闭您保持打开的所有文件,并执行任何其他必要的清理(例如,如果文件被标记为关闭时删除,那么它将删除该文件;请注意,那种事情是平台- 特定的)。

但是,另一个需要注意的问题是缓冲数据。大多数文件流在将数据写入磁盘之前先在内存中缓冲数据。如果您使用的是 stdio 库中的 FILE* 流,则有两种可能性:

  1. 通过调用exit(3) 函数或从main(隐式调用exit(3))返回,您的程序正常退出。
  2. 您的程序异常退出;这可以通过调用 abort(3)_Exit(3)、死于信号/异常等来实现。

如果您的程序正常退出,C 运行时将负责刷新所有打开的缓冲流。因此,如果您将缓冲数据写入未刷新的FILE*,它将在正常退出时被刷新。

相反,如果您的程序异常退出,任何缓冲的数据都将不会被刷新。当进程终止时,操作系统只会说“哦,天哪,你打开了一个文件描述符,我最好为你关闭它”;它不知道程序打算写入磁盘但没有写入内存中某处的一些随机数据。所以要小心。

【讨论】:

  • 在被操作系统重新获得之前,剩下的缓冲区会存在多长时间?
  • 我猜尽可能快,因为该缓冲区并不神奇 - 您的程序为它分配了内存,当您的程序作为正常清理的一部分崩溃时,操作系统应该释放该内存。
  • "[T]他的操作系统会在你之后清理"...这真的取决于操作系统。主要的桌面操作系统会这样做,但不能保证。
【解决方案2】:

C 标准说调用exit(或者,等效地,从main 返回)会导致所有打开的FILE 对象被fclose 关闭。所以这完全没问题,只是你失去了检测写入错误的机会。

编辑:对于异常终止没有这样的保证(abort,失败的assert,接收到默认行为是异常终止的信号程序——请注意,不一定有任何此类信号——以及其他实现定义的方式)。正如其他人所说,现代操作系统将清理所有外部可见资源,例如打开的操作系统级文件句柄,无论如何;但是,FILEs 在这种情况下可能不会被刷新。

肯定有一些操作系统在异常终止时没有清理外部可见的资源;它倾向于不强制执行“内核”和“用户”代码之间和/或不同用户空间“进程”之间的硬权限边界,仅仅是因为如果你没有这些边界,它可能不会可能 在所有情况下都可以安全地这样做。 (例如,考虑一下,如果你在 MS-DOS 中的打开文件表上写垃圾会发生什么,你完全可以做到。)

【讨论】:

  • 您能否提供参考,为什么这不是特定于操作系统的?
  • @Peter: C99, §7.20.4.3, ¶ 4: “接下来,所有打开的带有未写入缓冲数据的流都被刷新,所有打开的流都被关闭,所有由 tmpfile 函数创建的文件都被删除。”请注意,这仅适用于常规(exitreturn 来自 main)终止,_Exit & co 会发生什么情况。是实现定义的。
  • @MatteoItalia,谢谢,但如果进程由于其他原因意外终止,那么大多数现代操作系统无论如何都会清理句柄和内存。
  • @Peter:当然可以,但是,由于您想要一个与操作系统无关的关于这一事实的参考,因此我提供了标准给出的保证。 :)
【解决方案3】:

假设您在控制下退出,使用exit() 系统调用或从main() 返回,则打开的文件流在刷新后关闭。 C 标准(和 POSIX)要求这样做。

如果您退出失控(核心转储,SIGKILL)等,或者如果您使用_exit() or _Exit(),则不会刷新打开的文件流(但文件描述符最终会关闭,假设具有文件的类 POSIX 系统描述符 - 标准 C 不强制要求文件描述符)。请注意,_Exit() 是 C99 标准规定的,但 _exit() 是 POSIX 规定的(但它们在 POSIX 系统上的行为相同)。请注意,文件描述符与文件流是分开的。请参阅_exit() 的 POSIX 页面上关于“程序终止的后果”的讨论,了解在 Unix 下程序终止时会发生什么。

【讨论】:

  • “但文件描述符最终关闭” - 你确定吗?该标准规定“是否刷新具有未写入缓冲数据的打开流、关闭打开流或删除临时文件是实现定义的。” (C99 §7.20.4.4 ¶2)
  • 流没有关闭;文件描述符是。有(大)差异。当程序终止时,所有文件描述符都会关闭 - 请参阅 _Exit() 页面参考上的“程序终止的后果”我假设系统提供类似 POSIX 的语义;在纯标准 C 中,没有文件描述符。非 POSIX 系统的行为可能会有所不同。
  • 这就是我要说的:就 C 标准而言,_Exit() 上发生的事情是实现定义的(这些可能的行为之一是 POSIX 标准规定的行为) .
【解决方案4】:

当进程终止时,大多数现代操作系统(特别是内核)将释放所有句柄和分配的内存。

【讨论】:

  • 一个典型的现代操作系统至少可以做到这一点。我的理解是,如果应用程序没有正确退出,Windows 3.1 和其他系统会泄漏句柄。现在可能还有一些其他非常小的操作系统(也许是嵌入式系统)无法为您处理这些问题。
  • @KenSmith:这将适用于特定于操作系统的文件句柄,很难,因为标准(如@Zack 所说)保证在终止时所有 CRT FILE * 在常规退出的情况下都将关闭。
  • 好点;尽管如您上面所说,CRT 不保证如果应用程序退出异常 将关闭句柄。通常是由操作系统执行此操作 - 据我了解,有些非常古老或非常专业的操作系统无法提供这种保证。
猜你喜欢
  • 1970-01-01
  • 2011-04-02
  • 1970-01-01
  • 2014-01-18
  • 1970-01-01
  • 2012-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多