是否有保证和安全的方法从ANSI C FILE指针截断文件?

Jim*_*hen 11 c file-io

我知道ANSI C定义了fopen,fwrite,fread,fclose来修改文件的内容.但是,在截断文件时,我们必须转向操作系统特定的功能,例如,truncate()在Linux上,_chsize_s_()在Windows上.但在我们调用那些特定于操作系统的函数之前,我们必须通过调用来获取FILE指针的文件句柄fileno,也可以是非ANSI-C函数.

我的问题是:FILE*截断文件后继续使用是否可靠?我的意思是,ANSI C FILE层有自己的缓冲区,不知道文件是从下面截断的.如果缓冲的字节超出截断点,缓冲的内容是否会在执行时刷新到文件fclose()

如果不能保证,在编写Windows-Linux可移植程序时,使用文件I/O函数以及截断操作的最佳做​​法是什么?

类似的问题:当从文件句柄返回时查询文件大小fileno时,我以后调用时是否准确大小fclose()- 没有进一步的fwrite()

[编辑2012-12-11]

根据约书亚的建议.我得出结论,当前可能的最佳实践是:通过调用将流设置为无缓冲模式setbuf(stream, NULL);,然后truncate()或者_chsize_s()可以与流和平地工作.

无论如何,没有官方文档似乎明确证实了这种行为,无论是Microsoft CRT还是GNU glibc.

Gre*_*ods 6

POSIX方式......

ftruncate() 是你正在寻找的,它自2001年以来一直在POSIX基本规范中,所以现在它应该在每个现代POSIX兼容系统中.

请注意,ftruncate()对POSIX文件描述符(尽管可能具有误导性名称)进行操作,而不是STDIO流FILE句柄.另请注意,对STDIO流和对打开流的文件描述符进行操作的底层OS调用的混合操作可能会混淆STDIO库的内部运行时状态.

因此,为了ftruncate()安全地使用STDIO,如果您的程序可能已经写入相关流,则可能需要首先刷新任何STDIO缓冲区(with fflush()).这将避免STDIO在截断完成后尝试将其他未写入的缓冲区刷新到文件中.

然后,您可以使用fileno()STDIO流的FILE句柄来查找打开的STDIO流的基础文件描述符,然后您将使用该文件描述符ftruncate().您可以考虑将调用放在调用fileno()的参数列表中,ftruncate()这样您就不会保留文件描述符并意外地使用它,以及其他可能进一步混淆STDIO内部状态的方法.也许是这样的(比如将文件截断为当前的STDIO流偏移量):

/*
 * NOTE: fflush() is not needed here if there have been no calls to fseek() since
 * the last fwrite(), assuming it extended the length of the stream --
 * ftello() will account for any unwritten buffers
 */
if (ftruncate(fileno(stdout), ftello(stdout)) == -1) {
        fprintf(stderr, "%s: ftruncate(stdout) failed: %s\n", argv[0], strerror(errno));
        exit(1);
}
/* fseek() is not necessary here since we truncated at the current offset */
Run Code Online (Sandbox Code Playgroud)

另请注意,POSIX定义中的ftruncate()" 查找指针的值不应通过调用ftruncate()来修改 ",这意味着您可能还需要使用use fseek()来设置STDIO层(从而间接地设置文件描述符) )根据需要,或者到文件的新结尾,或者可能回到文件的开头,或者仍然在文件边界内的某个地方.(请注意,fseek()如果找到截断点,则不需要ftello().)

如果您按照上述步骤操作,则不必使STDIO流无缓冲,但当然这样做可能是使用fflush()(但不是fseek())的替代方法.

没有POSIX ....

如果您需要坚持严格的ISO标准C,比如C99,那么您没有可移植的方法将文件截断为除零(0)长度以外的给定长度.我在第7.21.3节(第2段)中说过的最新C11草案:

除非在7.21.5.3中定义,否则不会截断二进制文件.是否在文本流上写入导致关联文件被截断超过该点是实现定义的.

(和7.21.5.3描述了fopen()允许将文件截断为零长度的标志)

关于文本文件的警告是因为在具有文本和二进制文件的愚蠢系统上(而不是简单的POSIX风格的内容不可知文件),通常可以将文件写入将存储在文件中的文件在写入的位置,并EOF在下次读取文件时将其视为指示符.

其他类型的系统可能具有与POSIX不兼容的不同底层文件I/O接口,同时仍提供兼容的ISO C STDIO库.从理论上讲,如果这样的系统提供了类似的东西fileno(),ftrunctate()那么类似的程序也可以与它们一起使用,只要我们采取同样的措施避免混淆STDIO库的内部运行时状态.

关于查询文件大小....

您还询问通过查询fileno()返回的文件描述符找到的文件大小是否是成功调用后文件大小的准确表示fclose(),即使没有任何进一步的调用fwrite().

答案是:不要那样做!

如上所述,如果您不想混淆STDIO库的内部运行时状态,则必须非常小心地使用作为STDIO流打开的文件的POSIX文件描述符.我们可以在这里补充说,重要的是不要混淆自己.

找到作为STDIO流打开的文件的当前大小的最正确方法是寻找它的末尾,然后通过仅使用STDIO函数询问流指针的位置.