流与缓冲区

3

你好,我刚开始学C语言,正在阅读K&R。在里面关于文本流的定义“文本流是由换行符分隔的字符序列;每一行由0个或多个字符组成,后跟一个换行符”,这个定义让我感到困惑。

为了了解这些流,我接触到了一个新术语——缓冲区。

我只知道:

  • 在输入和输出设备之间的数据(字节或字符)连续流动形成了
  • 临时存储输入或输出数据的主内存区域称为缓冲区

我不确定我的理解是否正确,但这是我对这些概念的基本理解。

我想知道缓冲区和流实际上是什么以及这两个概念(即流和缓冲区)在C语言的具体实现中如何协同工作。

4个回答

3
在C语言中,你有三个流:stdin、stdout和stderr,你也可以将用fopen打开的文件视为流。stdin通常是键盘,stdout通常是显示器,stderr通常也是显示器。但它们不必如此,它们是硬件的抽象表示。
例如,如果你在银行ATM机上没有键盘而是数字键盘,那么stdin将是数字键盘;如果你没有显示器而是打印机,则stdout将是打印机。你可以通过对操作系统的调用来更改它们使用的硬件设备。你也可以通过对操作系统的调用来更改它们的行为,这超出了你提出的问题的范围。
所以,在某种程度上,可以将缓冲区看作是与流相关联的由操作系统分配的内存,用于保存从硬件组件接收到的数据。例如,当你在键盘上键入字符时,这些字符不是直接被你的IDE捕获的,它们会先移动到缓冲区,然后你才能读取缓冲区中的内容。
这就是为什么在代码开始与键盘交互之前必须按下回车键的原因,因为stdin是行缓冲的。控制权从你的程序传递到操作系统,直到它遇到发送控制权返回到你的程序的东西,在正常情况下,这将是换行符。
因此,在某种程度上可以这样理解:流是设备(键盘、显示器或硬盘上的文件),缓冲区是操作系统控制时保存数据的地方,然后你在处理数据时与缓冲区进行交互。
这种抽象允许你以一种通用的方式使用所有这些不同的东西,而不管它们是什么,例如:fgets(str, sizeof(str), STREAM) …其中的stream可以是任何输入流,无论是stdin还是文件。
更进一步地说,这就是为什么新手程序员会被scanf后面的fgets搞得很糊涂,因为scanf从缓冲区中读取int,但是在缓冲区中留下了\n。然后调用fgets读取scanf留在缓冲区中的\n,新手程序员就会想知道为什么他们无法输入任何数据。因此,对于C语言的学习来说,对流和缓冲区的好奇心将对你很有帮助。

嘿 @LEF,谢谢你的回答,让我终于得出了结论。它帮了我很多! - Uday Kiran
嗨,你能推荐一本书给我吗?这本书可以帮助我更深入地了解C语言的实现(即了解它在幕后的实际抽象工作原理)。 - Uday Kiran
@UdayKiran 我所使用的一直都是《C程序设计语言》,这本小书中包含了终身学习的内容。但有时候它还是有点过于“高级”,所以可以深入研究Linux内核。它是用C语言编写的,而且由于是开源的,你实际上可以查看抽象实现的方式。例如,当你调用fgets时,你可以看到幕后发生了什么。如果你正在寻找一个有趣的项目,可以编写一个聊天服务器/客户端设置,使用Beej的网络指南,它将帮助你开始进行Linux编程(并进一步提高你对流的知识)。 - LEF
@UdayKiran 关于服务器/客户端程序,深入研究select函数,你甚至可以使用Linux内核的timer_fd功能编写一个小游戏之类的东西,然后在学习文档方面,你只需要使用Linux程序员指南(man页面)以及互联网上的各种资源。你会发现stdin只是作为文件描述符实现的,等等。玩得开心。 - LEF
@UdayKiran在上面的评论中打错了一个字,而编辑时间已经过期了,只是为了明确起见,应该是timerfd,而不是timer_fd。 - LEF

1

这些实际上是相当好的工作定义。

在实际的C语言术语中,缓冲区是一个数组(通常是charunsigned char类型),用于存储数据,无论是作为输入操作的结果还是在发送到输出之前。该数组可以声明为固定大小的数组,例如:

char buffer[SOME_BUFFER_SIZE];

或者动态地,使用。
char *buffer = malloc( SOME_BUFFER_SIZE * sizeof *buffer );

使用动态内存的优点在于如果需要,可以调整缓冲区的大小;缺点是您必须管理该内存的生命周期。
对于文本输入/输出,通常会使用char数组;对于二进制输入/输出,通常会使用unsigned char 数组。
在通过网络通信的系统中,将数据以固定大小的“块”发送是相当普遍的,因此您可能需要多次读取或写入操作才能传输所有数据。想象一下Web服务器和浏览器-服务器以多个消息发送HTML,而浏览器将中间结果存储在缓冲区中。只有在接收到所有数据之后,浏览器才呈现页面。
Received from Web server       Stored in browser's input buffer
------------------------       --------------------------------
HTTP/1.1 200 OK \r\n           <!DOCTYPE HTML><html
Content-length: 20\r\n
<!DOCTYPE HTML><html

HTTP/1.1 200 OK \r\n           <!DOCTYPE HTML><html><head><title>This i
Content-length: 20\r\n
><head><title>This i

HTTP/1.1 200 OK \r\n           <!DOCTYPE HTML><html><head><title>This i
Content-length: 20\r\n         s a test</title></he
s a test</title></he

HTTP/1.1 200 OK \r\n           <!DOCTYPE HTML><html><head><title>This i
Content-length: 20\r\n         s a test</title></head><body><p>Hello, W
ad><body><p>Hello, W

HTTP/1.1 200 OK \r\n           <!DOCTYPE HTML><html><head><title>This i
Content-length: 19             s a test</title></head><body><p>Hello, W
orld!</body></html>            orld!</body></html>

没有理智的服务器会将HTML分成20个字符一组发送,但这可以说明为什么和如何使用缓冲区。

这是一个非常好的、友善的和有帮助的回答。 - Smart Humanism

0

定义并不差,实际上非常好。你可以从面向对象的角度补充一下,流使用缓冲区。

使用缓冲区可能是必要的,例如出于性能原因,因为每个系统调用都带有相对较高的成本。

特别是IO系统调用、硬盘或网络访问与内存访问时间相比较慢。如果读取或写入仅包含单个字节,则这些成本会累加。


0

输入/输出设备的两种常见抽象形式是:

流 - 在设备准备好时传输可变数量的字节。

块 - 传输固定大小的记录。

缓冲区只是保存正在传输的数据的内存区域。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接