【问题标题】:Default input and output buffering for fopen'd files?fopen'd 文件的默认输入和输出缓冲?
【发布时间】:2010-04-19 18:46:20
【问题描述】:

所以 FILE 流可以同时具有输入和输出缓冲区。您可以使用setvbuf 调整输出流(我不知道有任何方法可以使用输入缓冲区大小和行为)。

此外,默认情况下缓冲区是BUFSIZ(不确定这是 POSIX 还是 C 事物)。很清楚这对stdin/stdout/stderr 意味着什么,但是新打开的文件的默认值是什么?它们是否为输入和输出缓冲?或者也许只有一个?

如果是缓冲的,输出默认是块模式还是行模式?

编辑:我做了一些测试,看看 Jonathan Leffler 的回答如何影响现实世界的程序。看来,如果你先读然后写。写入将导致输入缓冲区的未使用部分完全丢弃。实际上,将进行一些查找以将内容保持在正确的文件偏移量处。我使用了这个简单的测试程序:

/* input file contains "ABCDEFGHIJKLMNOPQRSTUVWXYZ" */
#include <stdio.h>
#include <stdlib.h>

int main() {

    FILE *f = fopen("test.txt", "r+b");
    char ch;
    fread(&ch, 1, 1, f);
    fwrite("test", 4, 1, f);
    fclose(f);
    return 0;
}

导致以下系统调用:

read(3, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", 4096) = 27 // attempt to read 4096 chars, got 27
lseek(3, -26, SEEK_CUR)                 = 1        // at this point, I've done my write already, so forget the 26 chars I never asked for and seek to where I should be if we really just read one character...
write(3, "test", 4)                     = 4        // and write my test
close(3)                                = 0

虽然这些显然是实现细节,但就如何实现标准库而言,我发现它们非常有趣。感谢乔纳森您富有洞察力的回答。

【问题讨论】:

  • BUFSIZ 在 C 标准中 - 因此它也在 POSIX 中。
  • 未定义的行为:您必须在 fread()fwrite() 之间使用 fseek() - 如果您不这样做,实现可能会随心所欲(这可能是也可能不是您想要的) .

标签: c file stream buffer


【解决方案1】:

单个文件流有一个缓冲区。如果文件同时用于输入和输出,那么您必须确保在读取和写入(或写入和读取)操作之间执行适当的操作(fseek() 或等效操作)。

标准通道的缓冲行为取决于平台。

通常,当输出到终端时,stdout 是行缓冲的。但是,如果 stdout 是转到文件或管道而不是终端,它通常会切换到全缓冲。

通常,stderr 是行缓冲或非缓冲的,以确保看到错误消息(例如,即使程序即将崩溃)。

通常,标准输入是行缓冲的;这意味着您有机会编辑您的输入(在错误上退格等)。你很少会调整这个。同样,如果输入来自文件(或管道),则行为可能会有所不同。

新打开的文件通常会被完全缓冲。如果设备是终端,特定的实现可能会将其更改为行缓冲。

您的前提 - 有两个缓冲区 - 是不正确的。


C99 的第 7.19.3 节,它说:

在程序启动时,预定义了三个文本流,不需要显式打开 — 标准输入(用于读取常规输入)、标准输出(用于写入 常规输出)和标准错误(用于写入诊断输出)。和最初一样 打开,标准错误流没有完全缓冲;标准输入和标准 当且仅当可以确定流不引用时,输出流才被完全缓冲 到交互式设备。

因此,正如最初所说,stderr 要么是无缓冲的,要么是无缓冲的行缓冲的(它不是完全缓冲的)。

【讨论】:

  • 不允许两个缓冲区吗?如果在所有标准都说默认缓冲区是在第一次使用时分配的之后,如果它们只是两个缓冲区,那么实现起来会简单得多。我这样说是因为虽然很明显输入操作可以清除任何输出字符的缓冲区(缓冲区现在已清除,可用于输入...),但反过来处理起来更复杂。
  • 我还发现标准确实说stderr 默认情况下是无缓冲的。
  • 两个缓冲区会更加复杂(除了需要两倍的内存)。例如,如果您从输入缓冲区读取,执行 fseek() 回到您刚开始读取的位置,然后执行 write(),您必须确保将数据从(假设的)输出缓冲区复制到 (假设的)输入缓冲区。
  • 好吧,c++ 通过将 IO 缓冲区“绑定”到彼此来解决这个问题。在这种情况下,读取将自动刷新所有挂起的写入。相反的情况是我变得更复杂的地方。例如,如果您进行读取(请求 1 个字节,但它缓冲了 100 个字节),那么您进行了写入……它在哪里写入?它会简单地从输入缓冲区中丢弃其他 99 个读取字节吗?
  • @Evan:读取 1 个字节(缓冲 99 个尚未读取的字节);然后fseek()到当前位置(fseek(fp, 0L, SEEK_CUR);),然后写入10个字节;那么缓冲的 99 的前 10 个字节已经被践踏,但是可以读取新的数据。请注意,您不能使用终端执行此操作;你不能在终端上寻找,但你必须寻找在读写操作之间切换。因此,您将坚定地进入“未定义行为”的领域。但是对于磁盘文件,行为是明确定义的。 (写完后,你必须在fread()之前fseek(),但是写入的数据是要读取的。)
猜你喜欢
  • 2021-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-19
  • 1970-01-01
  • 2014-09-14
  • 2011-05-03
  • 2012-08-27
相关资源
最近更新 更多