【问题标题】:Understanding Buffering in C理解 C 中的缓冲
【发布时间】:2015-03-15 15:34:20
【问题描述】:

我很难理解缓冲的深度,尤其是在 C 编程中,我在这个主题上搜索了很长时间,但直到现在还没有找到令人满意的东西。

我会更具体一点: 我确实理解它背后的概念(即协调不同硬件设备的操作并最大限度地减少这些设备的速度差异),但我希望能更全面地解释这些和其他潜在的缓冲原因(我的意思是完整的)越长越好)如果能给出一些具体的例子来说明如何在 I/O 流中实现缓冲也是非常好的。

其他问题是我注意到我的程序没有遵循缓冲区刷新中的一些规则,因为这听起来像下面的简单片段:

#include <stdio.h>

int main(void)
{
    FILE * fp = fopen("hallo.txt", "w");

    fputc('A', fp);
    getchar();
    fputc('A', fp);
    getchar();

    return 0;
}

该程序旨在演示当第一个 getchar() 被调用时,即将发生的输入将立即刷新任意流,但这根本不会像我尝试它那样经常发生,并且我想要进行尽可能多的修改——它根本没有对于stdout(例如printf())不会发生这种情况,流在没有任何输入请求的情况下被刷新也否定了规则,因此我是否错误地理解了这条规则或者还有其他需要考虑的事情

我在 Windows 8.1 上使用 Gnu GCC。

更新:

我忘了问我在某些网站上阅读了人们如何引用,例如字符串文字作为缓冲区,甚至数组作为缓冲区;这是正确的还是我错过了什么? 也请解释一下。

【问题讨论】:

  • 对于示例,您不是在写入stdout,而是在写入任意文件。
  • 对不起我改了
  • 最后一个问题(来自更新);是的,数组(尤其是字符)被称为缓冲区,但这与 I/O 缓冲不同,尽管 I/O 缓冲通常使用缓冲区来保存数据,直到数据被写入或读取。
  • 为什么getchar() 来自stdin 会刷新您的输出文件(顺便说一句,您还没有关闭)?
  • 如果标准输出被写入文件、管道或其他非交互设备,则标准 I/O 库在通过getchar() 读取新数据之前不会刷新它。只有当标准输出进入交互式设备时,实现才会(通常)在读取标准输入之前刷新标准输出。即使这样,如果数据已经在输入缓冲区中,它也可能不会刷新标准输出。不过,我相信这是实现定义的行为。

标签: c windows gcc


【解决方案1】:

buffer 这个词在计算机科学中用于许多不同的事物。在更一般的意义上,它是临时存储数据的任何内存块,直到数据被处理或复制到最终目的地(或其他缓冲区)。

正如您在问题中暗示的那样,缓冲区有多种类型,但作为一个广泛的分组:

  1. 硬件缓冲区:这些缓冲区是在将数据移动到硬件设备之前存储数据的缓冲区。或者在从硬件设备接收数据直到被应用程序处理之前,将数据存储在缓冲区中。这是必需的,因为 I/O 操作通常具有内存和时序要求,而这些都由缓冲区来满足。想想直接读/写内存的 DMA 设备,如果内存设置不正确,系统可能会崩溃。或者声音设备必须具有亚微秒级精度,否则效果不佳。

  2. 缓存缓冲区:这些缓冲区是在写入/读取文件/设备之前对数据进行分组的缓冲区,因此通常会提高性能。

  3. 辅助缓冲区:您可以将数据移入/移出此类缓冲区,因为这对您的算法来说更容易。

案例 #2 是您的 FILE* 示例。想象一下,对 write 系统调用(Win32 中的WriteFile())的调用仅需要 1ms 的调用加上每个字节的 1us(请耐心等待,现实世界中的事情更复杂)。然后,如果你这样做:

FILE *f = fopen("file.txt", "w");
for (int i=0; i < 1000000; ++i)
    fputc('x', f);
fclose(f);

如果没有缓冲,这段代码将花费1000000 * (1ms + 1us),大约需要 1000 秒。但是,对于 10000 字节的缓冲区,将只有 100 个系统调用,每个调用 10000 个字节。那将是100 * (1ms + 10000us)。这只是 0.1 秒!

另请注意,操作系统会进行自己的缓冲,以便使用最有效的大小将数据写入实际设备。那将同时是一个硬件和缓存缓冲区!

关于您的刷新问题,文件通常在关闭或手动刷新时被刷新。某些文件,例如stdout 是行刷新的,也就是说,只要写入'\n',它们就会被刷新。 stdin/stdout 也很特别:当您从 stdin 读取时,stdout 被刷新。其他文件不变,只有stdout。如果您正在编写交互式程序,这将非常方便。

我的情况 #3 是例如当你这样做时:

FILE *f = open("x.txt", "r");
char buffer[1000];
fgets(buffer, sizeof(buffer), f);
int n;
sscanf(buffer, "%d", &n);

您使用缓冲区保存文件中的一行,然后解析该行中的数据。是的,您可以直接调用fscanf(),但在其他API中可能没有等效功能,而且您可以通过这种方式进行更多控制:您可以分析类型if line,skip cmets,count lines...

或者假设您一次接收一个字节,例如从键盘接收。您只需在缓冲区中累积字符并在按下 Enter 键时解析该行。这就是大多数交互式控制台程序所做的。

【讨论】:

  • 我想首先验证一些点是较慢的设备总是目标设备我的意思是例如在写入文件时,很明显硬盘比处理器和内存慢,但在接收输入时在硬盘上形成一个较慢的文件是很难在缓冲区上写入还是处理器处理接收到的数据更加清晰是数据积累得非常快然后以速度或其他方式处理 - 我希望我的问题足够清楚
  • @Lockon2000:这取决于您如何处理数据。例如,如果你想将新挖出的比特币写入文件,那么 CPU 会比 HD 慢。在这种情况下,您不会在写入之前缓冲几个 BT,您将立即写入并刷新。当然,您仍然有在保存之前计算 BT 的缓冲区。读者也一样:如果你的 CPU 丢弃数据,HD 会更慢,但如果你进行大量计算,那么 CPU 会更慢。它会起作用,因为任何理智的实现都会限制待处理缓冲区的最大大小并阻止最快的进程。
  • 从您上次编辑开始 - 首先:如果 stdout 仅在遇到换行符时刷新或仅在 stdin 输入时刷新(请参阅下面的我的评论)那么为什么在我的系统上 stdout正如我第二次提到的那样,即使请求任何输入或显示任何换行符也没有刷新:如果来自键盘的输入是一个又一个键的累积,然后也一个接一个地发送到处理器,我似乎误解了你最后几行的内容,因为程序不能只处理块单个字节然后块在哪里我的意思是缓冲背后的原因是什么
  • @Lockon2000:关于第一个:对不起,我只是不明白你在问什么(无意冒犯,但我发现你缺少标点符号很难阅读)。第二:在键盘情况下,需要缓冲区,因为sscanf() 需要一个缓冲区。没有函数可以解析一次占用一个字节的复杂语法。好吧,你可以写一个,只是在内部做 de 缓冲!
  • 是的,你是对的,我直到现在才注意到它,并且从现在开始不会忽略它。再次回答这个问题:好吧,让我们试着换一种说法;如果刷新标准输出的情况只是即将输入形式标准输入(特别是),或者遇到换行符;那么为什么我的系统在没有任何换行符或从标准输出请求输入的情况下刷新标准输出(例如在 printf("hay") 之后)?请查看本站第一个提到的规则gnu.org/software/libc/manual/html_node/…
【解决方案2】:

名词“缓冲区”实际上是指一种用法,而不是一个独特的东西。任何存储块都可以用作缓冲区。该术语有意在一般意义上与各种 I/O 函数一起使用,尽管 C I/O 流函数的文档倾向于避免这种情况。但是,以 POSIX read() 函数为例:“read() 尝试从文件描述符 fd 中读取最多 count 个字节到从 buf 开始的缓冲区”。在这种情况下,“缓冲区”仅表示将记录读取的字节的内存块;它通常实现为char[] 或动态分配的块。

使用缓冲区,尤其是与 I/O 结合使用,因为某些设备(尤其是硬盘)以中到大型块的方式读取效率最高,而程序通常希望以较小的块消耗这些数据。一些其他形式的 I/O,例如网络 I/O,可能天生就以块的形式出现,因此您必须记录每个完整的块(在缓冲区中),否则会丢失您尚未准备好使用的那部分。类似的考虑也适用于输出。

至于您的测试程序的行为,您希望演示的“规则”是特定于控制台 I/O 的,但只有一个涉及的流连接到控制台。

【讨论】:

    【解决方案3】:

    第一个问题有点太宽泛了。在很多情况下都会用到缓冲,包括实际使用前的消息存储、DMA 使用、加速使用等。简而言之,整个缓冲的事情可以概括为“保存我的数据,让我继续执行,而你对数据做一些事情”。

    有时您可以在将缓冲区传递给函数后对其进行修改,有时则不会。有时缓冲区是硬件,有时是软件。有时它们驻留在 RAM 中,有时驻留在其他内存类型中。

    所以,请提出更具体的问题。首先,使用维基百科,它几乎总是有帮助的:wiki

    至于代码示例,我没有发现任何提及在getchar 上刷新的所有输出缓冲区。文件的缓冲区一般会在三种情况下刷新:

    1. fflush() 或同等学历
    2. 文件已关闭
    3. 缓冲区溢出。

    由于这两种情况都不适合您,因此不会刷新文件(请注意,此列表中不是应用程序终止)。

    【讨论】:

    【解决方案4】:

    缓冲区是内存 (RAM) 中的一个简单小区域,该区域负责在发送到程序之前存储信息,只要我从键盘输入字符,这些字符就会存储在缓冲区中,并且作为很快我按下 Enter 键,这些字符将从缓冲区传输到您的程序中,因此在缓冲区的帮助下,所有这些字符都可以立即用于您的程序(防止滞后和缓慢)并将它们发送到输出显示屏

    【讨论】:

      猜你喜欢
      • 2013-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-13
      • 2012-03-09
      • 1970-01-01
      • 2021-09-22
      • 2014-09-15
      相关资源
      最近更新 更多