在二进制附加模式下,无法使用ftell()的问题

3

我有一些代码,在一个服务器上运行良好,但当它被移植到另一个系统时出现了问题。问题似乎出在这里:

给定我在其他地方定义的一个结构:

user1_type user1;              /* structure containing user data */
user1_type *user1_ptr=&user1;

这个例程将记录追加到文件末尾

if ((dfile=fopen(filename,"ab+"))==NULL)  
  error_message("Unable to open file for append.",filename,1);
 else { /* append data */
 user1.recid=ftell(dfile);               /* update file position */

 fwrite(user1_ptr,sizeof(user1),1,dfile);
 fflush(dfile);
 fclose(dfile);

我可以确认数据已经追加到文件中,但是user1.recid的值始终返回0——有什么想法吗?
更新:看起来问题并不是所有实现都需要在fopen()之后使用fseek()。当我追加时,在执行ftell()之前,我显然需要一个fseek(dfile, 0, SEEK_END);。但是,如果我想从文本或二进制文件的开头读取,是否也要在fopen()后立即放置一个fseek()?这是否因文件类型而异?

ftell返回的是long还是64位的类型?user1是如何定义的? - cleblanc
@cleblanc long int ftell(FILE *stream) 是一个用于获取文件流当前位置的函数。 - chux - Reinstate Monica
typedef struct { long recid; char dataid[5]; /* 用户1 */ .. } 用户1类型; - Trent Three
从我使用的ftell()文档中可以看到:"请注意,当文件以追加数据的方式打开时,当前文件位置是由最后一个I/O操作确定的,而不是下一个写入将发生的位置。" 必须先调用fseek()函数。 - Hans Passant
显然,在某些实现中,对于a+b的文件位置,无需进行寻址即可设置为EOF(FreeBSD)。 - Trent Three
3个回答

3
从MSDN ftell 的文档中可以了解到:

ftell() 返回的位置是相对于流的开头的偏移量。

如果在以附加方式打开文件之前还没有进行过I/O操作,则文件位置为文件开头。

这样,相对于开头,它给出了一个偏移量为0。
当您调用 user1.recid=ftell(dfile); 时,由于尚未对流进行任何I/O操作,因此 ftell() 返回0,表示文件指针位置在开头。

我在gcc 4.9.3/cygwin下没有看到这样的行为。你能提供支持文档吗? - R Sahu
这在过去一直有效,但是代码被移动到虚拟化环境中.. 这会导致任何变化吗? - Trent Three
请注意,当打开文件以追加数据时,当前文件位置是由最后一次I/O操作确定的,而不是下一次写入发生的位置。(MS VC2008) - Paul Ogilvie
1
@Trent Three 正如 R Sahu 在评论中提到的,这种行为可能是实现相关的(我不确定),我在答案中提供的行为来自 ftell 的 MSDN 文档,但是你可以使用 fseek() 在使用 ftell 之前将指针定位到文件末尾。 - Biruk Abebe
@TrentThree,你可以使用fseek(dfile,0,SEEK_END)将指针定位到末尾,然后调用ftell()。这不应该影响进一步写入文件的数据。 - Biruk Abebe
显示剩余4条评论

3
< p >这里ftell(dfile)的行为将是实现定义的。根据C11 7.21.3(以前的C标准有类似措辞):

< blockquote>

如果文件支持定位请求(例如磁盘文件,而不是终端),则与流相关联的文件位置指示器将定位在文件的开头(字符号零),除非使用追加模式打开文件,在这种情况下,文件位置指示器最初定位在文件的开头或结尾是实现定义的。


感谢澄清。我怀疑这可能是实现定义的。 - Biruk Abebe
请详细解释一下OP的“代码在服务器上运行正常,但在移动到另一个系统后出现问题”的情况。 - chux - Reinstate Monica
哇,谢谢...好的,我得想办法解决这个问题...发生的事情是在写入之前我需要知道文件位置。 - Trent Three
我之前在使用FreeBSD时,ftell()函数返回的结果和现在在CentOS上不一样了... 我需要用不同的模式/命令打开文件吗? - Trent Three
我相信Paul Ogilvie的回答是正确的-在获取位置之前必须执行手动搜索,这可能需要不同的实现方式。我正在进行测试。 - Trent Three
1
@TrentThree:我认为你不需要担心不同的实现方式 - 只需执行 fseek(dfile,0,SEEK_END) 即可。如果文件指针已经定位在那里,它就是一个无操作,并且对于从开头开始文件指针的实现,它将为您提供所需的行为。如果您以追加模式打开文件,则写入将始终在执行写入之前寻找文件末尾。 - Michael Burr

2

除了bkVnet的回答之外,您还必须使用fseek(dfile,0,SEEK_END)来寻找结尾,询问ftell位置并将其除以sizeof(user1_type)以获取记录ID(即文件中已有的记录数量,因此新记录加1)。


我认为您可能是正确的先生!这是我认为正在发生的事情...在我的以前的FreeBSD实现中,当打开一个文件进行追加时,位置已经移动到文件的末尾,所以ftell()以一种方式工作,但在CentOS下,您必须添加fseek(dfile,0,SEEK_END);才能获得ftell()给您的不是0的东西! - Trent Three
我相信我的代码问题是由于在追加时得到无效的记录编号引用所导致的。 - Trent Three
fread()函数会改变ftell()的位置吗?还是这取决于具体实现,我需要进行seek操作? - Trent Three
如果使用fopen(filename,“at”);打开文本文件,是否需要执行fseek? - Trent Three

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接