是否有一种有保证和安全的方法可以从 ANSI C 文件指针截断文件



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

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

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

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

[编辑 2012-12-11]

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

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

POSIX 方式....

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

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

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

然后,可以使用 STDIO 流FILE句柄上的fileno()来查找打开的 STDIO 流的基础文件描述符,然后将该文件描述符与ftruncate()一起使用。 您可以考虑将调用放在ftruncate()调用的参数列表中fileno(),这样您就不会保留文件描述符并意外使用它的其他方式,这可能会进一步混淆 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: %sn", argv[0], strerror(errno));
exit(1);
}
/* fseek() is not necessary here since we truncated at the current offset */

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

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

没有POSIX....

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

二进制文件不会被截断,除非在 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 函数询问流指针的位置。

零字节的无缓冲写入不应该在这一点上截断文件吗?

有关如何设置无缓冲:ANSI C 中的无缓冲 I/O

最新更新