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

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

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

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

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

[编辑2012-12-11]

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

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

POSIX方式……

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

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

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

然后,您可以在STDIO流的FILE句柄上使用fileno()来查找打开的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 */ 

另请注意, ftruncate()的POSIX定义说“ 不应通过调用ftruncate()修改查找指针的值 ”,这意味着您可能还需要使用fseek()来设置STDIO层(并且因此间接地将文件描述符)或者文件的新的一端,或者可能返回到文件的开头,或者仍然在文件的边界内的某个地方,根据需要。 (注意,如果使用ftello()找到截断点,则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函数询问流指针的位置。

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

有关如何设置unbuffered: ANSI C中的无缓冲I / O,请参阅此问题