首页 » 技术分享 » 天下有 C/C++ —— 第一章: fseek 函数在 “a” 模式下的特殊之处

天下有 C/C++ —— 第一章: fseek 函数在 “a” 模式下的特殊之处

 

问题

这几天在写808固件升级功能时遇到一个问题,头疼了几下下…

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NET_Debug(fmt, arg...) printf("%s-%d: "fmt"", __FILE__, __LINE__, ##arg);
#define JT_MAIN_TMP_UPGRADE_FILE "1234.bin"
typedef struct _jt_main_upgrade_file_head
{
    int iType;              /* 文件类型 JT_PARSE_UPGRADE_TYPE_E */
    int iAllNum;            /* 包总数 */
    int iWriteNum;          /* 已写入的包数 */
    int iFirstPackSN;       /* 第一包的流水号 */
    char szVersion[64];     /* 版本号 */
}JT_MAIN_UPGRADE_FILE_HEAD_S;
int main()
{
	int iret = 0;
	FILE * pFile;
	JT_MAIN_UPGRADE_FILE_HEAD_S stWriteHead;
	pFile = fopen(JT_MAIN_TMP_UPGRADE_FILE, "ab+");
	if (NULL == pFile)
	{
		return -1;
	}
	#if 1
	stWriteHead.iType = 1;
	stWriteHead.iAllNum = 98;
	stWriteHead.iWriteNum = 126;
	stWriteHead.iFirstPackSN = 10086;
	fseek(pFile, 0L, SEEK_SET);
	iret = fwrite(&(stWriteHead), 1, sizeof(JT_MAIN_UPGRADE_FILE_HEAD_S), pFile);
	NET_Debug("------1 - All num:%d, write:%d [iret:%d]\n", stWriteHead.iAllNum, stWriteHead.iWriteNum, iret);
	NET_Debug("ftell:%ld\n", ftell(pFile));
	#endif
	
	#if 1
	JT_MAIN_UPGRADE_FILE_HEAD_S stHead;
	memset(&stHead, 0, sizeof(JT_MAIN_UPGRADE_FILE_HEAD_S));
	fseek(pFile, 0L, SEEK_SET);
	iret = fread(&stHead, 1, sizeof(JT_MAIN_UPGRADE_FILE_HEAD_S), pFile);
	NET_Debug("------2 - All num:%d, write:%d [iret:%d] [%ld]\n", stHead.iAllNum, stHead.iWriteNum, iret, ftell(pFile));
	#endif
	
	fflush(pFile);	
	fclose(pFile);	
	
	return 0;
}

假设:事先存在 JT_MAIN_TMP_UPGRADE_FILE 文件,且文件大小为 0
假定的条件下,上述程序在首次运行时,写进去与读出来的数据是一样的。
但是,更改 “stWriteHead.iAllNum = 98;” 为 “stWriteHead.iAllNum = 78;” 时,再次编译执行,读出来的 iAllNum 值还是会为 98。
这是为什么呢 ?
从代码上看,写入数据时已经将位置偏移至文件开头,为何写的数据没写进去?
答案在于 fopen 函数。

pFile = fopen(JT_MAIN_TMP_UPGRADE_FILE, "ab+");

这里使用了 “ab+” 的方式打开文件,在 Linux 的 man 手册里,对这个模式有这样一个描述:

a+ — Open for reading and appending (writing at end of file). The file is created if it does not
exist. The initial file position for reading is at the beginning of the file, but output is
always appended to the end of the file.
翻译:在 “a+” 模式打开文件时,具备可读可写的权限,如果文件不存在则会创建文件,读取文件内容时从文件的头部读取,而写入数据时,总是在文件末尾写入

所以,在 “a+” 模式下,写入数据时,总是在文件末尾写入,而 fseek 的效果无效。当然也不是全然无效,假设一个文件原本大小为 100k,此时使用 “a+” 模式打开文件,那么写入数据时无法覆盖该文件前 100k 的数据,永远都是在 100k 后面的数据位置写入,对于这100k后面的数据,fseek 的效果是有效的。
如再写入100k的数据,此时文件总大小为 200k, 再执行

 fseek(pFile, 0L, SEEK_SET);

语句时,对于 fwrite 来说,此时文件指针位置将位于 100k 的位置。
故此,对于上述的例子而言,在第一次执行的时候,因为文件大小为0,所以第一次执行的结果是正确的。但是当第二次更改 iAllNum 值再次执行时,实际上 fwrite 函数写入的数据是以追加的方式写入,并非覆盖文件开头的数据,所以,第二次执行读出来的 iAllNum 数值还是 98。

所以,要想让文件指针指向真正的 0k 处(文件开头),则应使用另一种方式打开文件,即 “rb+”

r+ — Open for reading and writing. The stream is positioned at the beginning of the file.
翻译:打开的文件具有可读可写的权限,并且文件流指针指向文件开头处

如此,便可自由 fseek 读取与写入数据了。

齐烟九点 意尽天明

转载自原文链接, 如需删除请联系管理员。

原文链接:天下有 C/C++ —— 第一章: fseek 函数在 “a” 模式下的特殊之处,转载请注明来源!

0