如何正确的关闭已打开的文件

如果一个文件是以只读的方式打开的,那么忘记关闭的后果是:句柄泄露。通常来说这不是太大的问题。
如果一个文件是以可写的方式打开的,那么忘记关闭的后果是:除了句柄泄露以外,写进去的东西可能会丢失。并且,如果关闭了,但是忘记检查close(或fclose)函数的返回值,也会有丢数据的风险。

要注意点什么?

写文件的时候,要手动关,不要依赖于析构函数。用RAII来管理IO资源是非常愚蠢的。如果fclose出现在析构函数里,那么通常就是一个错误。因为析构函数不能抛异常,如果fclose返回非0的值,你很难把这个错误报告出去。出错不可怕,最可怕的是出错了但是你却不知道。比如,你的程序每天自动生成一个config文件然后自动推送到线上每台机器。结果有一天,生出来的config文件缺了一块,但是你却不知道,而这个config却被推送出去了。

什么时候fclose会返回非0值?

比如,硬盘满了。再比如,有些机器的硬盘会有一些临时故障,会导致写入偶尔失败。而这种临时故障不一定会产生报警。其实我们希望一旦有这样的问题发生,这些机器能赶紧被应用程序发现,然后从系统中剔除出去。

正确的该怎么写?

FILE* fd = fopen("xxx.txt","w");
if(!fd) return;
try{
  //do normal io stuffs
   ... 
}catch(std::exception& ex){
   if(fclose(fd)){
      //....?
   }
}
if(fclose(fd)) throw std::runtime_error("err");

问题来了,如果我们想把fclose失败这件事情报告出去,就得在处理异常的时候又抛出新的异常。该怎么办?遗憾的是,C++中对于这样的问题,并没有标准的做法。

Java是怎么处理这样的问题?

Java7为此特地推出了Suppressed Exceptions.
比如下面的代码:
package testjava;

import java.io.Closeable;
import java.io.IOException;

public class T1 {

 static class A implements Closeable{

  @Override
  public void close() throws IOException {
   throw new RuntimeException("Err");   
  }
  
 }
 
 static void foo() throws Exception{
  try(A a=new A()){
   throw new RuntimeException("er");
  }
 }
 
 public static void main(String[] args) throws Exception {
  try{
   foo();
  }catch(Exception ex){
   ex.printStackTrace();
  }
 }

}
会产生下面这样的输出:

java.lang.RuntimeException: er
at testjava.T1.foo(T1.java:19)
at testjava.T1.main(T1.java:25)
Suppressed: java.lang.RuntimeException: Err
at testjava.T1$A.close(T1.java:12)
at testjava.T1.foo(T1.java:20)
... 1 more

虽然代码看起来更简单了,但是这其实是把问题隐藏的更深了。谁在处理异常的时候会检查它内部有没有另一个异常呢?所以你其实没有机会把硬盘错误报告出去。

最终最重要的一句话:
“Don't trust your callers, nor your callees, always safeguard your code”

此博客中的热门博文

在windows下使用llvm+clang

少写代码,多读别人写的代码

tensorflow distributed runtime初窥