C/C++文件IO的陷阱

No Comments

       对于任何一个C/C++ 程序员,不和文件IO打交道是很少见的(可能有些嵌入式存在这种情况)。文件IO有很多种实现,有Ansi C的FILE指针、也有Ansi C++的fstream流。作为最基本的要求,估计每一个程序员都很熟悉之。此外对于某些特定OS,还有OS API,例如Windows 操作系统就有一套完整的文件IO支持。

       我一直以为对它了解的差不多,直到碰到几个恶心的臭虫后,才发现很多细节还掌握得不透彻,这里总结三个具体的情况:

  1. 打开文件的意外!

  2. 怎么Seek?

  3. 读写指针总是一致的吗?

打开文件的意外

       打开一个文件的方法可以有很多种情况,例如“只读”、“只写”、“Append”或者多种同时支持。目前主流的实现都是使用标志位来区分不同的情况、如果想同时使用多个标志位,则“按位或”之。

       有一次在本地写个Demo玩玩,使用“只写”标志位,文件名是随便写的,运行程序后突然想起此文件名实际存在,可是来不及了,文件早就清空了,害我花了好一些时间恢复之。

       对于“只写“的文件Open操作,首先会将指定的文件清空,如果文件实际存在。所以在没想清楚问题之前,慎用之!

怎么Seek

       有一次我使用“Append“方式打开一个文件,后来又想跑到文件前面去写几个字节,于是像模像样地加了个Seek操作。不过测试时发现,这狗日的死活不往前面写,而总是Append到文件尾。关于这个问题,网上貌似有个哥们也碰到这个问题,见这里【http://blog.csdn.net/zanget/archive/2009/08/09/4428854.aspx 】。

       如果你希望一个以“Append“标志位打开的文件能够Seek。请使用“读写“标志位打开,然后Seek到末尾。以Append标志位打开的文件是不能Seek的。

读写指针总是一致的吗

       什么读写指针?好像C语言中的FILE指针没有这个概念。是的,FILE读写指针是统一的,可以通过ftell获得当前的指针位置。不过在fstream流中,引入了读写指针两个概念,为了获得读/写指针的位置,必须通过两个独立的流成员函数tellg和tellp(ggetpput)。不过据我在Windows下测试,这两个值总是一样的,也许微软是基于效率的考量吧。

       尽管表现总是一致,可是既然C++标准有意将其分开,那程序员就不应该作这种假设。说不定那天发布个更新就不一致了,那程序维护的人还不跳楼!

       抱着这个思想,我也认为strstream也和fstream一样,读写指针一致。可实际却不一致,结果可想而知,我花了一周的时间去调试找臭虫。微软把strstream的读写指针分开,从另一方面验证了fstream不区分读写指针是基于性能考量的推测。因为strstream底层是内存,只不过做了一个文件IO的马甲,让不明真相的程序员以为是文件IO。

C++ 异常处理的一个注意点

No Comments

最近在代码中为了统一处理各种错误情况,决定使用C++ 中的异常。为了方便字符串输出,我在自定义异常类中使用了std的ostringstream。不过问题来了,编译时出错,提示大概如下:

Error 1 error C2248: ’std::basic_ios<_Elem,_Traits>::basic_ios’ : cannot access private member declared in class ’std::basic_ios<_Elem,_Traits>’ C:\Program Files\Microsoft Visual Studio 9.0\VC\include\sstream 451 Test_ostringstream
Error 2 error C2248: ’std::basic_ios<_Elem,_Traits>::basic_ios’ : cannot access private member declared in class ’std::basic_ios<_Elem,_Traits>’ C:\Program Files\Microsoft Visual Studio 9.0\VC\include\ostream 584 Test_ostringstream

后来发现是C++ 异常必须使用可拷贝的类或基本类型


c++ stream读写指针不一致导致的问题

No Comments

      代码中一直有一个bug,一个正常的流老是写不进去。后来才知道是流读写指针不一致惹得祸,具体是std::strstream。每次写入数据后,我通过tellg来获取当前的位置,而这个位置却一直没变。后来发现std::strstream流的读写指针并不一致。

#include <fstream>
#include <strstream>

//入口函数
int main(int args, char ** argv)
{
#if 1
	char buf[1024];
	std::strstream stream(buf,sizeof(buf),
		std::ios::out | std::ios::binary);
#else
	std::fstream stream("fas.xx",std::ios::in
		| std::ios::out | std::ios::binary
		| std::ios::trunc);
#endif
	int x = stream.tellg();
	int y = stream.tellp();
	char xxx[512];
	stream.write(xxx,sizeof(xxx));
	x = stream.tellg();
	y = stream.tellp();
	return 0;
}

      而其他流入fstream 读写指针则总是一致的,上面的代码演示了两种不同的情况。本人平台是windows 7 x64 + vs2008

C++ stream eof标志位导致的一个小bug

No Comments

     代码里面用到C++的流,不过程序一直有一个bug存在,也就是如果读一个istream到末尾后,接下来的seekg总是不成功(确切的讲是总是跳到最后,就算我使用代码“istream::seeg(0,std::ios::beg)”也一样)。

     后来查资料和看源码才发现,原来seekx操作会首先判断内部的一些标志位(eof也是其中的一个)。如果这些标志位有设置,那么它只是简单的调用成员函数clear来清除这些标志位。问题就出在这里,我没有判断是否已经到了结尾(使用istream::eof())。