2010年4月15日星期四

Win32环境下C语言标准文件操作的某怪异问题的分析

c语言文件操作方面知识,你的参考书上一般会讲到关于二进 制文件的在打开和读取时必须fopen的打开模式选项里加入字符b,比如当创建只读文件指针时,模式选项应为"rb"。对于关心这类无关痛痒的鸡毛蒜皮的 小事胜于绩点的同学(比如我)来说,一个类似于“二进制文件和文本文件究竟有什么区别“的问题可能会困扰你很久,因为通常情况下你会很悲剧的在看完整个一 章后发现对这两种文件的操作根本没有区别。这篇文章就是为这些好问些无聊问题的人准备的。
先讲个故事,跳过不妨碍技术方面的阅读。
前些时间我在做一个魔兽争霸的外挂,主要用来录制和重放游戏 中的视角切换动作。调试这种涉及到很多内存操作的程序是很难受的,因为就算设置了断点,错误的内存操作仍可能引起系统堆崩溃,在Windows环境下系统 堆崩溃约等于内核崩溃。程序即将完工,进行全功能测试时,却奇怪的遇到了频繁的系统崩溃,经过检查发现在数据文件中会不规律的出现一些奇怪的字符 0x0d。反复查找问题不得其解,后来想到0x0d的特殊含义(待会会讲到),研究了一番,终于得出了结论。
废话了这么多,现在讲点大家关心的。
大概有些人知道在Windows中换行物理上是用两个字符标 记的,即\n\r,这种乍看令人匪夷所思的设计有个算是可以接受的理由:主机时代电传打印机的换行需要先换行\n,然后回档\r(也叫回车,即把光标移到 行首),有点像电影里的老式打字机,每换一行都要把打印头移回行首。不过我印象中微软成立的时候电传打字机已经不怎么使用了,取而代之的是Apple的 Macintosh,IBM的PC和王安的WPS。
好像又扯远了。嗯。
大家也许记得当你使用printf输出字符时换行使用\n结尾的,事实是当程序向文件输出流输出\n时 Windows会在后面自动添加\r。
现在大家知道我想说什么了,在文本模式下如果输出了一个字节\n(0x0a)真正输出的是两个字符\n\r(多了一个字符,0x0d)。而 在二进制模式下这种越俎代庖的事系统是不会做的。
还没完。下面我们来看一下这种自动添加字符的机制是如何实现的。
代码1:
#include
int main()
{
      char str[200];
      sprintf(str,"So stupid\n");
      printf("%d\n", strlen(str));
      return 0;
}
输出:10
So stupid\n一共有十个字符,包括我们手动添加的一个换行符\n,strlen函 数可以正确给出这个字符串的长度。这个程序说明了,在字符串输出之前\r是不会自动添加的。
代码2:
#include
int main()
{
      FILE *fp = fopen("test", "wb");
     
      fprintf(fp, "So stupid\nWin32 file operation\n");
      fclose(fp);
     
      return 0;
}
用记事本打开会发现字符串并没有正常的换行。fprintf看来没涉及这滩浑水。C语 言的标准库并没有改,因此字符串的表达和写入不需要考虑多出的一个字符,对于开发者来说这无疑是一个好消息。
通过上面两段代码,我们可以假设Windows系 统是在文件抽象层进行了\n之后\r的添加工作。然后我们用以下实验来进行验证。
代码3:
#include
int main()
{
      FILE *fp;
      fp = fopen(“test”,”w”);
      fputc(fp,'\n');
      fclose(fp);
      return 0;
}
记事本打开文件,可以正常显示空行,说明系统自动添加了\r, 即使是我们明确要求写一个字符0x0a,所以上述假设得证。
总结:
关于为什么要区分文本模式和二进制模式的问题上面也有提到, 不过就连我也不认为那是个什么可以接受的解释。这种不合理的设置在POSIX标准中并不存在,因此广大的*nix程 序员(当然也包括Mac OS程序员)很幸运的不需要考虑这个问题。然而作为Win程序员你可能就 有点小悲剧了。。。希望读完这篇冗长的文章后大家能免于在这种非技术性问题上浪费时间,也希望微软能尽快取消这个不必要的选项。