关于C中I/O缓冲区的解释
用户程序调用C标准I/O库函数读写文件或设备,而这些库函数要通过系统调用把读写请求传给内核,最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE
结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc
/fputc
为例,当用户程序第一次调用fgetc
读一个字节时,fgetc
函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc
,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc
时,fgetc
函 数会再次进入内核读1K字节到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像CPU、Cache和内存之间的关系一样,C标准库之 所以会从内核预读一些数据放在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核 读数据要快得多。另一方面,用户程序调用fputc
通常只是写到I/O缓冲区中,这样fputc
函数可以很快地返回,如果I/O缓冲区写满了,fputc
就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备,这称为Flush 操作,对应的库函数是fflush
,fclose
函数在关闭文件之前也会做Flush操作。
下图以fgets
/fputs
示意了I/O缓冲区的作用,使用fgets
/fputs
函数时在用户程序中也需要分配缓冲区(图中的buf1
和buf2
),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。
C标准库的I/O缓冲区:
C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时,不同类型的缓冲区具有不同的特性。
- 全缓冲
-
如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。
- 行缓冲
-
如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。
- 无缓冲
-
用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
下面通过一个简单的例子证明标准输出对应终端设备时是行缓冲的。
#include <stdio.h> int main()
{
printf("hello world");
while(1);
return 0;
}
运行这个程序,会发现hello world
并没有打印到屏幕上。用Ctrl-C终止它,去掉程序中的while(1);
语句再试一次:
$ ./a.out
hello world$
hello world
被打印到屏幕上,后面直接跟Shell提示符,中间没有换行。
我们知道main
函数被启动代码这样调用:exit(main(argc, argv));
。main
函数return
时启动代码会调用exit
,exit
函数首先关闭所有尚未关闭的FILE *
指针(关闭之前要做Flush操作),然后通过_exit
系统调用进入内核退出当前进程。
在上面的例子中,由于标准输出是行缓冲的,printf("hello world");
打印的字符串中没有换行符,所以只把字符串写到标准输出的I/O缓冲区中而没有写回内核(写到终端设备),如果敲Ctrl-C,进程是异常终止的,并没有调用exit
,也就没有机会Flush I/O缓冲区,因此字符串最终没有打印到屏幕上。如果把打印语句改成printf("hello world\n");
,有换行符,就会立刻写到终端设备,或者如果把while(1);
去掉也可以写到终端设备,因为程序退出时会调用exit
Flush所有I/O缓冲区。在本书的其它例子中,printf
打印的字符串末尾都有换行符,以保证字符串在printf
调用结束时就写到终端设备。
我们再做个实验,在程序中直接调用_exit
退出。
#include <stdio.h>
#include <unistd.h> int main()
{
printf("hello world");
_exit(0);
}
结果也不会把字符串打印到屏幕上,如果把_exit
调用改成exit
就可以打印到屏幕上。
除了写满缓冲区、写入换行符之外,行缓冲还有一种情况会自动做Flush操作。如果:
用户程序调用库函数从无缓冲的文件中读取
或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据
那么在读取之前会自动Flush所有行缓冲。例如:
#include <stdio.h>
#include <unistd.h> int main()
{
char buf[20];
printf("Please input a line: ");
fgets(buf, 20, stdin);
return 0;
}
虽然调用printf
并不会把字符串写到设备,但紧接着调用fgets
读一个行缓冲的文件(标准输入),在读取之前会自动Flush所有行缓冲,包括标准输出。
如果用户程序不想完全依赖于自动的Flush操作,可以调fflush
函数手动做Flush操作。
#include <stdio.h> int fflush(FILE *stream);
返回值:成功返回0,出错返回EOF并设置errno
对前面的例子再稍加改动:
#include <stdio.h> int main()
{
printf("hello world");
fflush(stdout);
while(1);
}
虽然字符串中没有换行,但用户程序调用fflush
强制写回内核,因此也能在屏幕上打印出字符串。fflush
函数用于确保数据写回了内核,以免进程异常终止时丢失数据。作为一个特例,调用fflush(NULL)
可以对所有打开文件的I/O缓冲区做Flush操作。
总结一下:C语言中的I/O缓冲区是这样的:
对于printf函数,行缓冲,每次输出有换行符或输出超过缓冲区大小的数据时,会在屏幕上输出数据。
有两个例外:1.当从无缓冲文件(或数据流)中读数据输出;
2.当printf后跟了scanf,由于scanf是行缓冲流,而且调用scanf会引发系统写数据到内
核,所以printf会自动flush。
对于scanf函数,行缓冲,每次输入时会把数据连同末尾的换行符送进缓冲区,然后缓冲区会有一个“指针”,始终指向当前读到的那个字符(即“当前字符指针”)。当把字符从缓冲区中送入内存中的具体变量中时,清除缓冲区中的该字符。(敲回车输入的换行符在缓冲区中当做空格符' '处理)。值得注意的是,每次scanf时,先检测缓冲区的“当前字符指针”之后是否有未读字符。
这样,连续scanf %c数据时,就有可能把数据尾的换行符直接赋给接下来本要输入的字符变量。
举例:(思考)
一个小程序,其中有以下几句:
char c1,c2;
scanf("%c",&c1);
printf("%c",c1);
scanf("%c",&c2);
printf("%c",c2);
这样,run以后,输入c1,敲回车以后就不能输入c2,程序直接结束了,为什么?
后来修改一下,
把“scanf("%c",&c2);
printf("%c",c2);”改成了:
scanf("%s",&c2);
printf("%c",c2);
却可以,为什么?%s和%c有什么不同?(%s读取第一个非空字符)
还有一种方法:
scanf("%c",&ch);改为scanf("
%c",&ch);%c前面加一个空格,这是为什么?
有还不清楚的请直接在留言中提问,谢谢。
关于C中I/O缓冲区的解释的更多相关文章
- x264中重要结构体参数解释,参数设置,函数说明 <转>
x264中重要结构体参数解释http://www.usr.cc/thread-51995-1-3.htmlx264参数设置http://www.usr.cc/thread-51996-1-3.html ...
- 转:"在已损坏了程序内部状态的XXX.exe 中发生了缓冲区溢出"的一种可能原因
我的问题跟原作者的问题差不多.头文件和DLL不匹配导致的. 原文链接:http://blog.csdn.net/u012494876/article/details/39030887 今天软件突然出现 ...
- 对Android项目中的文件夹进行解释
对Android项目中的文件夹进行解释: · src:里面存放的是Activity程序,或者是以后的其他组件,在此文件夹之中建立类的时候一定要注意,包名称不能是一级. · gen:此文件夹中的内容是自 ...
- Java进阶(十五)Java中设置session的详细解释
Java中设置session的详细解释 简单通俗的讲session就是象一个临时的容器,用来存放临时的东西.从你登陆开始就保存在session里,当然你可以自己设置它的有效时间和页面,举个简单的例子: ...
- 如何处理Android中的防缓冲区溢出技术
[51CTO专稿]本文将具体介绍Android中的防缓冲区溢出技术的来龙去脉. 1.什么是ASLR? ASLR(Address space layout randomization)是一种针对缓冲区溢 ...
- 使用clr 调用C#编写的dll中的方法的全解释
使用clr 调用C#编写的dll中的方法的全解释1.数据库初始化:将下面这段代码直接在运行就可以初始化数据库了exec sp_configure 'show advanced options', '1 ...
- 图像处理中的数学原理具体解释21——PCA实例与图像编码
欢迎关注我的博客专栏"图像处理中的数学原理具体解释" 全文文件夹请见 图像处理中的数学原理具体解释(总纲) http://blog.csdn.net/baimafujinji/ar ...
- 图像处理中的数学原理具体解释20——主成分变换(PCA)
欢迎关注我的博客专栏"图像处理中的数学原理具体解释" 全文文件夹请见 图像处理中的数学原理具体解释(总纲) http://blog.csdn.net/baimafujinji/ar ...
- python中self与__init__怎么解释能让小白弄懂?
python中self与__init__怎么解释能让小白弄懂? 这个问题其实没那么简单. 只说一下自己的理解. python 里所有的 object 都有三个属性, 标识(identity), 类型( ...
随机推荐
- pro mvvm 读书笔记
一.分离关注点 目的是确保每一个模块值有单一的,明确的目的,不需要去负责其他的功能.单一的目的也称为关注点. 1.1依赖 引用程序集对于依赖来说不是必须的.依赖关系可能也存在于一个代码单元要知道另一个 ...
- JAVA用POI读取和创建2003和2007版本Excel完美示例
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import ja ...
- Fiddler2 java代码拦截设置
jre -DproxySet=true -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8888 Or: jre -DproxySet=true -Dproxy ...
- 分布式理论(4):Leases 一种解决分布式缓存一致性的高效容错机制(转)
作者:Cary G.Gray and David R. Cheriton 1989 译者:phylips@bmy 2011-5-7 出处:http://duanple.blog.163.com/blo ...
- 【转】使用 Jmeter 做 Web 接口测试
最近总结了一下在接口测试方面的知识与心得,在这里与大家分享一下,如有说的不对的地方请多多指正. 接口测试概述 定义 API testing is a type of software testing ...
- Qt中的串口编程之一
QtSerialPort 简介 功能介绍 SerialPort SerialPortInfo 源代码 编译和安装 配置编译环境 Perl只是在Qt5的时候才需要Qt4的情况下可以不配置 使用如下推荐步 ...
- php 添加数据库的几种方法
最简单的 <?php $con = mysql_connect("localhost","root","root"); if (!$c ...
- bedtools 的安装与使用
1) 安装 bedtools 提供了3种安装方式 从google code 下载源代码进行安装 利用系统中的包管理工具进行安装, 比如cnetos 下的yum, ubuntu下的apt-get, ma ...
- linux下安装dovecot
Dovecot是一个开源的,为Linux/Unix-like系统提供IMAP,POP3服务的软件.主要是为了安全产生的,不管大小应用,Dovecot都是一个非常优秀的选择.它非常快,配置简单,不需要专 ...
- PHP curl_setopt函数用法介绍补充篇
1.curl数据采集系列之单页面采集函数get_html 单页面采集在数据采集过程中是最常用的一个功能 有时在服务器访问限制的情况下 只能使用这种采集方式 慢 但是可以简单的控制 所以写好一个常用的c ...