12

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

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

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

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

[编辑 2012-12-11]

按照约书亚的建议。我得出的结论是,当前可能的最佳实践是:通过调用将流设置为无缓冲模式setbuf(stream, NULL);,然后truncate()_chsize_s()可以与流和平相处。

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

4

2 回答 2

6

POSIX 方式....

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

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

因此,为了ftruncate()安全地使用 STDIO,如果您的程序可能已经写入有问题的流,则可能需要首先刷新任何 STDIO 缓冲区(使用)。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 */

另请注意,POSIX 定义ftruncate()说“不应通过调用 ftruncate() 来修改查找指针的值”,因此这意味着您可能还需要使用 usefseek()来设置 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下次读取文件时将被视为指示符。

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

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

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

答案是:不要那样做!

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

查找作为 STDIO 流打开的文件的当前大小的最正确方法是查找文件的末尾,然后仅使用 STDIO 函数询问流指针在哪里。

于 2012-12-11T04:06:07.020 回答
3

零字节的无缓冲写入不是应该在那时截断文件吗?

请参阅此问题以了解如何设置无缓冲:ANSI C 中的无缓冲 I/O

于 2012-12-07T02:41:13.850 回答