现在的位置: 首页 > 综合 > 正文

windows环境下使用fread遇到的奇怪问题

2017年11月18日 ⁄ 综合 ⁄ 共 1659字 ⁄ 字号 评论关闭

使用C库函数fopen、fread进行文件的操作,是大家都熟悉的处理方式。但最近在公司的实际项目中发现了一个很奇怪也很有趣的现象。


公司的项目使用VC编写前置程序,对客户传来的报文进行字符集转码操作。在转码过程中使用了临时文件。问题出现在读取临时文件过程中。代码很简单:

	FILE *fp;
	
	<span style="color:#ff0000;">fp = fopen(utf8file,"r");</span>
	if (fp == NULL)
	{
		return -3;
	}
	
	char * utf8buff;
	int nlen=0;
	
	fseek(fp,0,SEEK_END);
	
	nlen = ftell(fp);

	printf("长度nlen[%d]\n",nlen);
	
	if (nlen <1 )
	{
		fclose(fp);
		printf("utf-8报文[%s]为空\n",utf8file);
		return -4;
		
	}
	fseek(fp,0,SEEK_SET);
	
	utf8buff = (char *) malloc(nlen + 1);
	
	memset(utf8buff,0,nlen + 1);
	
	int rtnlen = fread(utf8buff,sizeof(char),nlen,fp);

客户上送的文件为UTF-8字符集的XML报文,内容为客户的账务处理信息,有户名等中文内容。由于客户之前只传来一条明细报文,这个程序运行多年都没有问题。但最近客户上送的明细报文增加为多条,就产生了一些奇妙的变化。

产生的现象是utf8buff这个缓冲区的内容最后总会是一段乱码,乱码内容就是文件最后的内容。比如要读出的文件内容为“abc回车换行def回车换行gh”,缓冲区里就会变成“abc回车def回车ghgh”。我能理解的原因是由于fopen时以字符串方式打开,回车换行自动转换为了回车,少了两个字符。但fread不负责将之后的内容清空吗?为什么原文件的内容还留在原来的位置?就像海水退下去后,礁石显露了出来。

网上搜了一回,发现有个同学碰到了和我类似的问题,总结得很好。具体内容请看这里

摘抄一部分:

貌似在这些承诺中,有两点没有被提及:
1,将读取到的内容存入指定内存区域后,是否要在结尾补上字符串终结符 '\0' ?
2,如果因为 EOF 或者其他原因,读到的字节数 M 小于指定字节数 COUNT, fread 是否仅仅对大小为
M 的内存区域做出修改呢?
首先可以确定第一点是否定的, fread 并非仅仅为文本流读取设计,它不会自己去补这个终结符;第二点则是我遇到的奇怪问题所在:文档承诺对传入指针参数指向的内存区域写入,并且预期的写入尺寸由 count 参数指定,而读取到的实际尺寸小于预期尺寸一律视为一种异常情况,包括
EOF 导致的截断。异常情况下文档的约定**并没有承诺**仅仅写入 M 大小的区域,而不污染 (M, COUNT] 的区域。尽管 Linux 下 glibc 做了这件没承诺的事情,但 libc 并没有这个责任。
所以,如果不考虑 EOF 以外的读取异常,用 fread 读取一个最终读到尺寸可能小于预期尺寸的文件,应该手动补上终结符 '\0' 以解决余下的区域可能的污染问题。

对C/C++库函数、或操作系统的API函数,我们可以有一个认知:它们只做它们承诺过的事,没承诺过的事,会不会多做,不确定。

比如这个例子“abc回车换行def回车换行gh”,以字符串方式fread,实际返回内容会比原文件少两个字符,那么缓冲区中的这最后两个字节会是什么值呢?编译器或操作系统会把这两个字节赋值为0x00吗?从此次事件来看,结论是,不一定。因为客户上送的文件,哪怕只改动其中的任何一个字节的内容的值,乱码就消失了,空余出来的结尾字节的值就变成了0x00。

库函数与操作系统API的实现方式的确比较诡异,也许我们深入到源码内部,才能最终找到问题的原因所在。在透彻地了解机理之前,我们还是老老实实的,用bin方式读取文件吧。

另:有的同学也遇到过fread的怪问题,扩展阅读,也许能为大家提供更多的思路,以后遇到类似的问题,就不要再掉坑里了。请看这里

【上篇】
【下篇】

抱歉!评论已关闭.