0. 序曲
写这篇短文的起因是,前两天想去天大的acm在线系统找几道题做做。为什么呢?因为本人天大毕业,这个天大呢可是中国最早的大学,原名北洋大学堂,这可绝对是货真价实的第一所大学。给大家推荐推荐啊,学风那是相当的好。
扯多了,还是回到本来的话题上。上了acm系统之后,就先看了1001。那道题的意思是输入一些正整数(以EOF结束),把对应的字符输出。这个简单,程序很快就出来了:
#include <stdio.h>
int main()
{
int c;
while(scanf("%d", &c) != EOF)
{
putchar(c);
}
return 0;
}
程序运行,输入103 102 105 107<enter>
输出gfik。
当时运行完之后马上想,为什么不是输入一个数字马上输出一个字符呢,因为看程序确实是这样的逻辑,只要不是EOF,就会输出。又一想,对了,是缓冲的问题。想起来APUE里边说得stdin应该是行缓冲的,另外,可以用setbuf,setvbuf设定流的缓冲。于是想将stdin设成无缓冲的。于是程序变成这样:
#include <stdio.h>
int main()
{
int c;
setbuf(stdin, NULL);
while(scanf("%d", &c) != EOF)
{
putchar(c);
}
return 0;
}
可是编译运行,还是老样子,没有变化。想了想,没想出是啥原因,于是开始google和APUE。终于算是明白了些,整理在这儿。
声明:
本文很大部分内容来自APUE--UNIX环境高级编程。
1. 缓冲类型。
标准库提供缓冲是为了减少对read和write的调用。提供的缓冲有三种类型(整理自APUE):
在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。
术语flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。
在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流--例如标注输入(stdin)和标准输出(stdout)--是行缓冲的。
标准I/O库不缓存字符。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。
ISO C要求:
但是,这并没有告诉我们当标准输入/输出在涉及交互设备时,它们是无缓存的还是行缓存的;也没有告诉我们标准错误应该是行缓存的还是无缓存的。不过,大多数实现默认的缓存类型是这样的:
2. 改变默认缓存类型
可以通过下面的函数改变缓存类型(摘自APUE):
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode,
size_t size);
这些函数必须在流打开之后、但是未对流做任何操作之前被调用(因为每个函数都需要一个有效的文件指针作为第一个参数)。
利用setbuf,可以打开或者关闭缓存。为了打开缓存,buf参数必须一个大小为BUFSIZ的缓存,BUFSIZ是定义在stdio。h中的常量。&lt;&lt;ISO/IEC
9899&gt;&gt;要求:BUFSIZ至少为256。如果要关闭缓存,可以将buf设成NULL。
利用setvbuf,我们可以设定缓存类型。这是通过mode参数指定的。
关于这两个函数,可以看下表(摘自APUE):
Function
|
mode
|
buf
|
Buffer and length
|
Type of
buffering
|
setbuf
|
|
non-null
|
user buf of length
BUFSIZ
|
fully buffered or line buffered
|
NULL
|
(no buffer)
|
unbuffered
|
setvbuf
|
_IOLBF
|
non-null
|
user buf of length size
|
fully buffered
|
NULL
|
system buffer of appropriate length
|
_IOFBF
|
non-null
|
user buf of length size
|
line buffered
|
NULL
|
system buffer of appropriate length
|
_IONBF
|
(ignored)
|
(no buffer)
|
unbuffered
|
需要注意的是:如果在函数内为流分配了自动变量作为缓存,那么在退出之前需要将流关闭。因此最好让系统自己分配缓存,这些缓存在流关闭的时候会自动被释放。
3.如果清理输入缓存
关于这点可以参看comp.lang.c
FAQ的Question12.26b:
Q: If fflush won't work, what can I
use to flush input?
A: It depends on what you're trying to do. If you're
trying to get rid of an unread newline or other unexpected input after calling
scanf (see questions 12.18a-12.19), you really need to rewrite or replace the
call to scanf (see question 12.20). Alternatively, you can consume the rest of a
partially-read line with a simple code fragment like
while((c =
getchar()) != '\n' && c != EOF)
/* discard */ ;
(You may also
be able to use the curses flushinp function.)
There is no standard way to
discard unread characters from a stdio input stream. Some vendors do implement
fflush so that fflush(stdin) discards unread characters, although portable
programs cannot depend on this. (Some versions of the stdio library implement
fpurge or fabort calls which do the same thing, but these aren't standard,
either.) Note, too, that flushing stdio input buffers is not necessarily
sufficient: unread characters can also
accumulate in other, OS-level input buffers. If you're trying to actively
discard input (perhaps in anticipation of issuing an unexpected prompt to
confirm a destructive action, for which an accidentally-typed ``y'' could be
disastrous), you'll have to use a system-specific technique to detect the
presence of typed-ahead input; see questions 19.1 and 19.2. Keep in mind that
users can become frustrated if you discard input that happened to be
typed too quickly.
References: ISO Sec. 7.9.5.2
H&S Sec.
15.2
4. 几点需要注意的地方
- 对输入流进行fflush操作是无定义的。
- 无缓存并不意味着一个个的那样处理输入,而是说当操作系统返回它们时,对于标准库函数来说它们是立即可用的。因为还可能有操作系统级甚至是硬件级的缓存,这些并不是setbuf可以控制的。
- 另外可以参考这里(我就是最先从这里开始看的)。还有这里。我从后面那个链接摘录一些重要的下来:
setbuf() has to do with the
delivery of bytes between the
C library FILE* management layer and the OS I/O
layer.
Calls to fread(), fgets(),
fgetc(), and getchar() work within
whatever FILE* buffered data is available,
and when that data
is exhausted, the calls request that the FILE* buffer be
refilled
by the system I/O layer.
When full buffering is turned
on, that refill operation results in the
FILE* layer requesting that the
operating system hand it a full
buffer's worth of data; when buffering is
turned off, that
refill operation results in the FILE* layer requesting that
the
operating system return a single character.
...setting an input stream to
be unbuffered
does NOT tell the operating
system to tell the device driver
to go into any kind of "raw"
single-character mode. There are
system-specific calls such as ioctl() and
tcsetterm() that
control what the device driver will
do.