2006年6月30日星期五

关于fstream.open()中使用in|out|app模式打开文件总是失败的问题


  其实是以前找到的一个问题了,不过今天提到了编译器的版本,突然想起了它。在国内的网站上似乎很难搜索到关于这个问题的报告,因此写出来供分享。

  事情的起因是服务器版本的升级。以前我们的程序都是在一台RH 7.3的内部服务器上编译的。编译之后的程序拿到正式服务器(RHEL AS 3.0)上使用非常正常,因此一直以来都是采取的这样的方式。
  后来新增了一台服务器,是x86_64的CPU,我们就打算安装最新的RHEL AS 4.0 Update 3 for x86_64版本,以获取最新的技术带来的优势。虽然最终因为短期内Oracle中的数据和设计无法升级到预计的10g Release 2 for x86_64版本,而导致升级计划暂时搁置,但在升级过程中,就发现以前的代码出现了许多问题。
  当然,2.96的编译器和3.4.x的编译器之间,肯定是有着不小的差别,因此代码会有问题这早就在我们的预料之中。大部分的错误都是容易发现和容易排除的(另文介绍),但其中有一个棘手的问题,就是本文要说到的,fstream.open()的打开模式问题。

  在以前的代码中,我们一直使用in|out|app的模式来进行日志文件的打开。采用这种模式,如果文件不存在,那么系统会创建它,如果文件存在,那么打开它并从最后面开始写信息,同时也可以从中读信息。可以说,针对那种以当前日期命名的日志文件,这种打开模式组合是再合适不过了。
  然而,编译器升级之后,我们发现这样的打开模式再也打不开任何的文件了。文件既不会被创建,也不会被打开,即使是这个文件已经存在也不行。去掉app或去掉in,都可以正常打开文件,但是这样就牺牲了追加或是读取文件的能力。相比而言,out|app的打开模式对于我们的日志类来说应该是可以接受的,但是为了了解这个问题的根源,我们还是花了不少力气。

  还是google好用。当我们发现这个问题确实是编译器的升级带来的,与操作系统内核、文件系统读写权限、路径/文件名等因素无关之后。我们很快就在google上搜索到了这篇文章:
  http://gcc.gnu.org/ml/gcc-bugs/2002-04/msg01055.html
  这位老兄发现了和我们类似的问题,并上报给了gcc那边。当然,他的工作比我们要做得细致得多。他仔细测试了多种打开模式的组合,并在多种平台、多种编译器版本上做了测试,并得到了结果。没有耐性点开帖子看或无法访问国外站点的用户可以看看下面这个测试结果:

GCC 2.95.2 i686-pc-linux-gnu
open("ios::in", O_RDONLY|0x8000)
open("ios::out", O_WRONLY|O_CREAT|O_TRUNC|0x8000, 0664)
open("ios::in|ios::out", O_RDWR|O_CREAT|0x8000, 0664)
open("ios::in|ios::ate", O_RDONLY|O_CREAT|0x8000, 0664)
open("ios::out|ios::app", O_WRONLY|O_APPEND|O_CREAT|0x8000, 0664)
open("ios::out|ios::ate", O_WRONLY|O_CREAT|0x8000, 0664)
open("ios::out|ios::trunc", O_WRONLY|O_CREAT|O_TRUNC|0x8000, 0664)
open("ios::in|ios::out|ios::app", O_RDWR|O_APPEND|O_CREAT|0x8000, 0664)
open("ios::in|ios::out|ios::ate", O_RDWR|O_CREAT|0x8000, 0664)
open("ios::in|ios::out|ios::trunc", O_RDWR|O_CREAT|O_TRUNC|0x8000, 0664)
GCC 2.95.2 sparc-sun-solaris2.8
open("ios::in", O_RDONLY)
open("ios::out", O_WRONLY|O_CREAT|O_TRUNC, 0664)
open("ios::in|ios::out", O_RDWR|O_CREAT, 0664)
open("ios::in|ios::ate", O_RDONLY|O_CREAT, 0664)
open("ios::out|ios::app", O_WRONLY|O_APPEND|O_CREAT, 0664)
open("ios::out|ios::ate", O_WRONLY|O_CREAT, 0664)
open("ios::out|ios::trunc", O_WRONLY|O_CREAT|O_TRUNC, 0664)
open("ios::in|ios::out|ios::app", O_RDWR|O_APPEND|O_CREAT, 0664)
open("ios::in|ios::out|ios::ate", O_RDWR|O_CREAT, 0664)
open("ios::in|ios::out|ios::trunc", O_RDWR|O_CREAT|O_TRUNC, 0664)

GCC 3.0.4 i686-pc-linux-gnu or sparc-sun-solaris2.8
open("ios::in", O_RDONLY)
open("ios::out", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::in|ios::out", O_RDWR)
open("ios::in|ios::ate", O_RDONLY)
open("ios::out|ios::app", O_WRONLY|O_APPEND|O_CREAT, 0666)
open("ios::out|ios::ate", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::out|ios::trunc", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::in|ios::out|ios::ate", O_RDWR)
open("ios::in|ios::out|ios::trunc", O_RDWR|O_CREAT|O_TRUNC, 0666)

Sun CC 5.0 sparc-sun-solaris2.8
open("ios::in", O_RDONLY)
open("ios::out", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::in|ios::out", O_RDWR)
open("ios::in|ios::ate", O_RDONLY)
open("ios::out|ios::app", O_WRONLY|O_APPEND|O_CREAT, 0666)
open("ios::out|ios::ate", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::out|ios::trunc", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("ios::in|ios::out|ios::app", O_RDWR|O_APPEND|O_CREAT, 0666)
open("ios::in|ios::out|ios::ate", O_RDWR)
open("ios::in|ios::out|ios::trunc", O_RDWR|O_CREAT|O_TRUNC, 0666)

  他得到了几乎所有有用的fstream.open()打开模式组合所对应的实际操作。可以看到,编译器版本的影响确实很大。
  可以注意到,gcc 3.0.4与2.95.2的测试结果相差不小。而且,最重要的是,有效的测试结果中,没有in|out|app这种组合。作者在最后发问,这种打开模式组合没有出现,是bug还是别的原因?可想而知,所谓“没有出现”,就是指这种打开模式组合总是会失败,相当于无法使用。就和我们遇到的情况一样。

  再次的搜索,发现一年之后gcc那边出现了这样的帖子:
  http://gcc.gnu.org/ml/gcc-prs/2003-04/msg00771.html
  这是gcc开发组的人(应该是吧)发的,具体地说,是paolo@gcc.gnu.org。帖子大概是针对一个bug报告的回应,内容就是说in|out|app这种打开模式组合根据ISO标准是非法的,无效的,因此这个bug报告可以close了。ISO标准,应该就是gcc 3.x所遵循的C++标准了。

  至此,真相水落石出。我们也就安心改用out|app模式来写日志了。

没有评论:

发表评论