如果在C程序中没有调用fclose()函数会发生什么?

81
首先,我知道使用fopen()打开文件并且不关闭它非常不负责任和不规范。这只是出于好奇,所以请承让 :)
我知道,如果一个C程序打开了许多文件但没有关闭它们中的任何一个,最终fopen()将开始失败。除了代码本身外,是否还有其他可能引起问题的副作用?例如,如果我有一个程序打开一个文件,然后退出而不关闭它,会对运行该程序的人造成问题吗?这样的程序会泄漏任何东西(内存、文件句柄)吗?在程序完成后再次访问该文件可能会出现问题吗?如果程序连续运行多次会发生什么?

4
不需要所有的免责声明。没有调用fclose而退出并不像使用goto一样糟糕,至少在任何情况下都不会更加糟糕。这完全取决于具体情况。 - R.. GitHub STOP HELPING ICE
4个回答

110
只要你的程序正在运行,如果你不关闭打开的文件而持续打开文件,最有可能的结果就是你的进程会用尽可用的文件描述符/句柄,然后尝试打开更多的文件将最终失败。在Windows上,这也会阻止其他进程打开或删除你已经打开的文件,因为默认情况下,文件以独占共享模式打开,防止其他进程打开它们。
一旦你的程序退出,操作系统就会为你进行清理。当操作系统终止你的进程时,它会关闭你遗留打开的任何文件,并执行任何必要的清理工作(例如,如果标记了“关闭即删除”的文件,则会删除该文件;请注意,此类事情是特定于平台的)。
然而,还需要小心的是缓冲数据。大多数文件流在将数据写入磁盘之前将其缓存在内存中。如果你正在使用stdio库中的FILE*流,那么有两种可能:
1.你的程序正常退出,通过调用exit(3)函数或从main返回(隐式地调用exit(3))。
2.你的程序异常退出;这可以通过调用abort(3)_Exit(3),死亡信号/异常等方式发生。
如果你的程序正常退出,C运行时库会负责刷新任何已打开的缓冲流。因此,如果你有缓冲数据写入到未被刷新的FILE*中,它将在正常退出时被刷新。
相反地,如果你的程序异常退出,任何缓冲数据都不会被刷新。当进程终止时,操作系统只会说“哦,亲爱的,你留下了一个文件描述符没有关闭,我最好为你关闭它”,它不知道程序意图写入磁盘但未能完成的随机数据存放在内存中的位置。所以要小心。

1
在操作系统重新获得剩余缓冲区之前,你有什么猜测它会存在多长时间? - user10607
2
我建议尽快释放缓冲区,因为该缓冲区并非神奇 - 您的程序为其分配了内存,当您的程序崩溃时,操作系统应将该内存作为正常清理的一部分进行释放。 - Xupicor
1
“操作系统会为您清理”…这实际上取决于操作系统。主要的桌面操作系统会这样做,但并不保证。 - Some programmer dude

13

C标准规定调用 exit(或等效地从 main 返回)会关闭所有打开的 FILE 对象,就像通过 fclose 一样。因此,这是完全可以的,但您将放弃检测写入错误的机会。

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

确实有一些操作系统在异常终止时不会清理外部可见资源;这往往与不在“内核”和“用户”代码之间强制执行硬特权边界以及/或不在不同的用户空间“进程”之间强制执行边界有关,仅仅是因为如果您没有这些边界,则在所有情况下安全地这样做可能是不可能的。(例如,请考虑在MS-DOS中写入打开文件表时会发生什么情况,因为您完全有能力这样做。)


1
你能提供一个参考吗?为什么这不是特定于操作系统的呢? - Peter
5
@Peter:C99,§7.20.4.3,¶4:“接下来,所有具有未写入缓冲数据的打开流都将被刷新,所有打开的流都将被关闭,并且由tmpfile函数创建的所有文件都将被删除。”请注意,这仅适用于正常(从main中的exitreturn)终止,对于_Exit等情况是实现定义的。 - Matteo Italia
@MatteoItalia,谢谢,但也要知道,如果进程因其他原因意外终止,大多数现代操作系统仍会清理句柄和内存。 - Peter
1
@Peter:没问题,但是既然你想要一个与操作系统无关的参考,我只提供标准所给出的保证。 :) - Matteo Italia

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

“但是文件描述符最终被关闭了”- 你确定吗?标准规定“刷新未写入缓冲数据的打开流、关闭打开的流或删除临时文件是实现定义的。”(C99 §7.20.4.4 ¶2) - Matteo Italia
流没有被关闭,文件描述符被关闭了。这是一个(很大的)区别。当程序终止时,所有的文件描述符都会被关闭——请参见“程序终止的后果”在 _Exit() 页面引用中。我假设系统提供类似 POSIX 的语义;在纯标准 C 中,没有文件描述符。对于非 POSIX 系统,行为可能有所不同。 - Jonathan Leffler
这就是我的意思:就C标准而言,_Exit() 函数的行为是实现定义的(其中一个可能的行为是由POSIX标准规定的)。 - Matteo Italia

0

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


2
现代操作系统至少会这样做。我了解到,早期的Windows 3.1和其他类似的操作系统如果应用程序没有正确退出,就会泄漏句柄。现在可能有一些其他非常小型的操作系统(例如嵌入式系统)不会为您处理这些问题。 - Ken Smith
@KenSmith:这适用于特定于操作系统的文件句柄,因为标准(如@Zack所说)保证在正常退出的情况下关闭所有CRT FILE * - Matteo Italia
1
不错的观点;尽管如你所说,CRT不能保证在应用程序异常退出时句柄会被关闭。通常是操作系统完成这个任务 - 我了解到有一些非常老或非常专业的操作系统无法做出这样的保证。 - Ken Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接