依睛(IT blog) 我回来了,PHP<-->C/C++ LINUX

笨鸟

统计

积分与排名

友情连接

最新评论

关于流和缓冲区的理解以及一般标准输入问题的解决方法小结 一(转)

关于流和缓冲区的理解以及一般标准输入问题的解决方法小结 一(转)

先看两个问题(以下程序运行环境为WIN2000+DEV-C++4.9.9.2):
1、执行以下程序:
代码:
/*****************************************************
Name:            copy_bar.c
Copyright:      kernelxu
Author:           kernelxu
Date:             2005-08-02 11:20
Description:    show the progress bar while doing copy
****************************************************/

#include <stdio.h>

int main(void)
{
   char c = '\0';
   int  i = 0;
   long j = 0L;

   putchar('\n');
   puts("Copy?(y/n): \n");
   for (; (c = getchar()) == 'y' || c == 'Y'; )
   {
       printf("Coping......");
       for (i = 0; i <= 100; i++)
       {
           printf("%%%3d", i);
           printf("\b\b\b\b");
           j = 0L;
           while (j < 20000000)
           {
                 j++;            /*delay相当于linux中sleep(n)*/
           }
       }
       puts("\nFinished!\n");
       puts("Copy?(y/n): \n");
   }
   system("pause");     

   return 0;
}

该程序实现一项简单的状态条显示功能(当然只是玩玩而已),但是它不能循环进行下去,程序执行一次循环就跳出了。

2、
代码:
/*****************************************************
Name:             login_example.c
Copyright:        kernelxu
Author:           kernelxu
Date:             2005-08-02 14:17
Description:      taking login for example to say sth about I/O buffer
*****************************************************/


#include <stdio.h>
#include <string.h>
#define NAME_MAX 10
#define USER_NAME "kernelxu\0"
#define PASS_WORD 123456

int main(void)
{
   char userName[NAME_MAX] = {'\0'};
   unsigned long  passWord = 0UL;
   

   for(; ; )
   {
         printf("Login:");
         gets (userName);
         printf("Password:");
         scanf("%ld", &passWord);
         if(passWord != PASS_WORD || strcmp(userName, USER_NAME) != 0)
         {
             printf("Login Incorrect!\n");
             continue;
         }
         break;
   }
   printf("[%s@localhost ~]$", userName);
   getch();
   
   return 0;
}

该程序只要输入的用户名超过NAME_MAX-1值或者password输入的不是正整数,就无法登录了。

以上两例程序之所以存在很大的问题,主要是在处理标准输入缓冲及选择输入函数上不够慎重。本文先总结一下流(stream)、缓冲区(buffer)的概念,然后重点说一下这类问题的解决之道。



一、流(stream):这里讨论的是标准I / O术语流(请勿将其与系统V的STREAMS I/O系统相混淆,后者可参见Stevens的《UNIX环境高级编程》第十二章)。
引用:
1)(K&R 《The C Programming Language》P241)
引用:
A stream is a source or destination of data that may be associated with a disk or other peripheral.

中文版(徐宝文等译)翻译为:
引用:
流(stream)是与磁盘或其它外围设备关联的数据的源或目的地。

2) (http://www.cs.cf.ac.uk/Dave/C/no ... 1820000000000000000
引用:
Streams are a portable way of reading and writingdata. They provide a flexible and efficient means of I/O. A Stream is afile or a physical device (e.g. printer or monitor) which ismanipulated with a pointer to the stream.

翻译为:
引用:
流是(表达)读写数据的一种可移植的方法,它为一般的I/O操作提供了灵活有效的手段。一个流是一个由指针操作的文件或者是一个物理设备,而这个指针正是指向了这个流。

3)(ISO/IEC《 ISO/IEC 9899:1999 (E) 》)
引用:
Input and output, whether to or from physical devicessuch as terminals and tape drives, or whether to or from filessupported on structured storage devices, are mapped into logical datastreams, whose properties are more uniform than their various inputsand outputs.

翻译为:
引用:
不管是交互于诸如终端和磁带驱动器之类的物理设备,还是存取于由结构化存储设备支撑的文件,输入和输出(信息)都被映射为逻辑数据流,而流的属性却远不是诸多输入输出属性的统一。

4)(Kenneth .A.Reek著,徐波译《C和指针(Pointers On C)》第十五章P229)
引用:
ANSIC进一步对I/O的概念进行了抽象。就C程序而言,所有的I/O操作只是简单地从程序移进或移出字节的事情。因此,毫不惊奇的是,这种字节流便被称为流(stream)。程序只需要关心创建正确的输出字节数据,以及正确地解释从输入读取的字节数据。特定I/O设备的细节对程序员是隐藏的。

5)(引自Sergio C. Carbone 《Learning C For Real》P33)
引用:
What is meant by the stream concept?
- A stream is a logical, device independent way of handling many peripheral devices.
- Most peripheral devices are different and would require special programming
techniques.
- Streams mask this difference from the programmer and allows the programmer to
handle most peripheral devices in the same way.
- Streams are buffered interfaces that are thought of as a series of characters read one
at a time.
- PCs are based on a stream architecture.

翻译为:
引用:
流的概念意味着什么呢?
--流是独立于设备之外而操纵外设一种逻辑手段。
--大多数外设都是互异的,所以(操纵)它们需要专门的编程技术
--流对程序员隐藏这些不同点,而准许他们以同样的方式来处理大多数外设。
--考虑到一连串的字符需要一次读一个,流(相当于)是具有缓冲作用的接口。
--个人计算机都是基于流架构的。

各大权威对流的说法有些不一致,我认为流既是数据的源或目的地的抽象,也是源和目的地之间流动信息的表示。但流起码都暗含以下的几个方面:
1、流是一个抽象的概念,是对信息的一种表达;在程序中,流就是对某个对象输入输出信息的抽象。就像运输工具是对一切运动载体的抽象一样。
2、流是一种“动”的概念,静止存储在介质上的信息只有当它按一定的序列准备“运动”时才称为流。“从程序移进或移出字节”就是“动”的表现。静止的信息具有流的潜力,但不一定是流,就像没有汽油不能行走的汽车一样,它具有运输工具的潜力,但它还不是运输工具(因为它很有可能被当作房子来用了,我就在大街上看见有精明的商人用火车车厢来做酒吧)。
3、流有源头也有目的地;程序中各种移动的信息都有其源和目的,记得编程(特别是汇编)时,老是要确定好某个操作的源操作数和目的操作数。借用佛教一言也即是:“万物皆有因果”,这也就像长江一样,西自唐古拉,而东去太平洋。在高速公路上飞跑的汽车,它必有其出发地和目的地。
4、流一定带有某种信息,没有任何内容的流带着自身来表达“空”信息。就像运输工具一样,它不运货的时候就运着自己这一身的零件(包括驾驶员)并把一样东西运到目的地,那就是它自己和一个“跑空车”的信息。流有最小的信息单元就是二进制位,含有最小的信息包就是字节,C标准库提供两种类型的流:二进制流(binary stream)和文本流(textstream)。二进制流是有未经处理的字节构成的序列;文本流是由文本行组成的序列。而在著名的UNIX系统中,文本流和二进制流是相同的(identical)。
5、流有源头也有目的地,那么它必定与源头和目的地相关联。但人们操作流的时候,最关心的还是其目的地,也就是一个定向(orientation)的意思,就像司机运货一样,它首要关心的问题是目的地,而非起点(操作者都知道)。在C语言中,通过打开流来关联流及其目的地,使用的函数是fopen(),该函数返回一个指向文件的指针(FILE *),该指针包含了足够的可以控制流准确地到达目的地的信息。
FILE是一个结构体(摘自TC2.0中stdio.h文件)
代码:
/* Definition of the control structure for streams
*/
typedef struct  {
       short           level;          /* fill/empty level of buffer */
       unsigned        flags;          /* File status flags    */
       char            fd;             /* File descriptor      */
       unsigned char   hold;           /* Ungetc char if no buffer */
       short           bsize;          /* Buffer size          */
       unsigned char   *buffer;        /* Data transfer buffer */
       unsigned char   *curp;          /* Current active pointer */
       unsigned        istemp;         /* Temporary file indicator */
       short           token;          /* Used for validity checking */
}       FILE;                           /* This is the FILE object */

   将它称为流控制结构体(control structure forstreams)真好表现出其功能来。举个例子就好像一卡车司机要把货物运到X公司,公司主管就会给他一张地图及X公司的基本信息,这些材料所提供的信息如果足够的话,那么它就能指导着司机准确地将货物送达了。C中FILE这个结构体所起的作用就好像是运输公司把一切有用的指导信息封装起来的档案袋一样。而已有关联的流要终止这种关联,就必须关闭流,使用的函数是fclose(),就像运货公司若不再给X公司运货了,那么他们就必须要终止合作协议了。
   这里要注意的是:C语言中stdin、stdout、stderr分别是标准输入流、标准输出流及标准出错流的逻辑目的,他们都默认对应相应的物理终端。在程序运行伊始,不需要进行open()操作,流自动打开。

关于C语言流(stream)的具体细节可参看:
引用:
a) K&R 《The C Programming Language》
b) Kenneth .A.Reek著,徐波译《C和指针(Pointers On C)》
c) Stevens的《UNIX环境高级编程》
d) 《ISO/IEC 9899:1999 (E)》及ANSI C手册


二、缓冲区(Buffer):
  为了匹配计算机快速设备和慢速设备间的通信步伐,计算机中大量使用硬件缓冲区(如CPU中的Cache,内存相对于硬盘和CPU),流是传输信息的一种逻辑表示,对流的各种不同操作也可能存在使用缓冲的需求。但是这里的buffer只是一种逻辑概念,不是物理设备。缓冲区存在于流与具体的设备终端或者存储介质上的文件之间。就好像运货到一个公司里一样,合同上的要求是运到X公司,但是实际上是真的把货物运到X公司的总部大楼吗?不是。应该是运到X公司的仓库中。这里的仓库就有点像我们所说的缓冲区了。也可以这么说,流运动到目的,先经过的是缓存区。有时候是不是可以这样理解:stdin、stdout和stderr就是标准输入流、标准输出流及标准出错流的缓存区。

1、(引自《 Rationale for International Standard Programming Languages C 》)
引用:
Buffering. UNIX allows the program to control theextent and type of buffering for various purposes. For example, aprogram can provide its own large I/O buffer to improve efficiency, orcan request unbuffered terminal I/O to process each input character asit is 20 entered. Other systems do not necessarily support thisgenerality. Some systems provide only line-at-a-time access to terminalinput; some systems support program-allocated buffers only by copyingdata to and from system-allocated buffers for processing. Buffering isaddressed in the Standard by specifying UNIX-like setbuf and setvbuffunctions, but permitting great latitude in their implementation. Aconforming library need neither 25 attempt the impossible nor respondto a program attempt to improve efficiency by introducing additionaloverhead.


2、(摘自A. D. Marshall《Programming in C UNIX System Calls and Subroutines using C》
http://www.cs.cf.ac.uk/Dave/C/)

引用:
Stream I/O is BUFFERED: That is to say a fixed``chunk'' is read from or written to a file via some temporary storagearea (the buffer). This is illustrated in Fig. 1. NOTE the file pointeractually points to this buffer.



Fig.1(见文末)

引用:
Stream I/O Model This leads to efficient I/O butbeware: data written to a buffer does not appear in a file (or device)until the buffer is flushed or written out. (\n does this). Anyabnormal exit of code can cause problems.

    标准I / O提供缓存的目的是尽可能减少使用read和write调用的数量。它也对每个I /O流自动地进行缓存管理,避免了应用程序需要考虑这一点所带来的麻烦。不幸的是,标准I /O库令人最感迷惑的也是它的缓存。不同类型缓存往往使人在进行I/O操作时不知所措。标准I / O提供了三种类型的缓存:全缓存、行缓存、无缓存。

1、 (引自ISO/IEC《 ISO/IEC 9899:1999 (E) 》)

引用:
When a stream is unbuffered, characters are intendedto appear from the source or at the destination as soon as possible.Otherwise characters may be accumulated and
transmitted to or fromthe host environment as a block. When a stream is fully buffered,characters are intended to be transmitted to or from the hostenvironment as a block when a buffer is filled. When a stream is linebuffered, characters are intended to be transmitted to or from the hostenvironment as a block when a new-line character is encountered.Furthermore, characters are intended to be transmitted as a block tothe host environment when a buffer is filled, when input is requestedon an unbuffered stream, or when input is requested on a line bufferedstream that requires the transmission of characters from the hostenvironment.


2、(引自Mike Banahan, Declan Brady and Mark Doran《 The C Book 》)

引用:
There are three types of buffering:
Unbuffered Minimum internal storage is used by stdio in an attempt to send or receive data as soon as possible.
Linebuffered Characters are processed on a line-by-line basis. This iscommonly used in interactive environments, and internal buffers areflushed only when full or when a newline is processed.
Fully buffered Internal buffers are only flushed when full.
Thebuffering associated with a stream can always be flushed by usingfflush explicitly. Support for the various types of buffering isimplementation defined, and can be controlled within these limits usingsetbuf and setvbuf.


3、说的最清楚的要数Stevens的《UNIX环境高级编程》了,以下摘自Stevens的《UNIX环境高级编程》第五章:

引用:
标准I / O提供了三种类型的缓存:
(1) 全缓存。在这种情况下,当填满标准I /O缓存后才进行实际I / O操作。对于驻在磁盘上的文件通常是由标准I / O库实施全缓存的。在一个流上执行第一次I / O操作时,相关标准I/ O函数通常调用m a l l o c(见7 . 8节)获得需使用的缓存。
术语刷新( f l u s h)说明标准I /O缓存的写操作。缓存可由标准I / O例程自动地刷新(例如当填满一个缓存时),或者可以调用函数ff l u sh刷新一个流。值得引起注意的是在U N I X环境中,刷新有两种意思。在标准I /O库方面,刷新意味着将缓存中的内容写到磁盘上(该缓存可以只是局部填写的)。在终端驱动程序方面(例如在第11章中所述的t c f l u sh函数),刷新表示丢弃已存在缓存中的数据。
(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I / O库执行I /O操作。这允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I /O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。对于行缓存有两个限制。第一个是:因为标准I /O库用来收集每一行的缓存的长度是固定的,所以只要填满了缓存,那么即使还没有写一个新行符,也进行I /O操作。第二个是:任何时候只要通过标准输入输出库要求从( a )一个不带缓存的流,或者( b)一个行缓存的流(它预先要求从内核得到数据)得到输入数据,那么就会造成刷新所有行缓存输出流。在( b )中带了一
个在括号中的说明的理由是,所需的数据可能已在该缓存中,它并不要求内核在需要该数据时才进行该操作。很明显,从不带缓存的一个流中进行输入( ( a )项)要求当时从内核得到数据。
(3) 不带缓存。标准I / O库不对字符进行缓存。如果用标准I / O函数写若干字符到不带缓存
的流中,则相当于用w r i t e系统调用函数将这些字符写至相关联的打开文件上。标准出错流s t d e r r通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
ANSI C要求下列缓存特征:
(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
(2) 标准出错决不会是全缓存的。
但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还
是行缓存的,以及标准输出是不带缓存的,还是行缓存的。S V R 4和4 . 3 + B S D的系统默认使用下列类型的缓存:
• 标准出错是不带缓存的。
• 如若是涉及终端设备的其他流,则它们是行缓存的;否则是全缓存的。


  我们经常要用到标准输入和输出,而ANSIC对stdin、stdout和stderr的缓存特征没有强行的规定,以至于不同的系统可能有不同的stdin、stdout和stderr的缓存特征。目前主要的缓存特征是:stdin和stdout是行缓存;而stderr是无缓存的。



[/img][img][/img]







posted on 2009-01-17 21:47 向左向右走 阅读(395) 评论(0)  编辑 收藏 引用 所属分类: C/C++学习资料库

只有注册用户登录后才能发表评论。