头文件stdio.h

C标准I/O库函数基于系统级I/O函数实现,文件是用一个指向特定结构的指针来标识的,这个特定结构就是 FILE结构,它描述了包含文件描述符在内的一组信息。即,它将打开文件抽象为一个类型为FILE的“流”,它是对文件描述符和流缓冲区的抽象。
FILE结构在头文件stdio.h中描述,此外, stdio.h文件中还对其他与标准I/O有关的常量、数据结构、函数等进行了定义。
对文件的操作实际是对文件在内存的缓冲区的操作。
关于FILE结构在VC6中有以下定义
#ifndef _FILE_DEFINED
struct _iobuf {
    char *_ptr; //文件输入的下一个位置
    int _cnt; //当前缓冲区的相对位置
    char *_base; //指基础位置(即是文件的其始位置) 
    int _flag; //文件标志
    int _file; //文件描述符fd
    int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
    int _bufsiz; //文件缓冲区大小
    char *_tmpfname; //临时文件名
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif
以下面的简化版程序为例说明:
#define NULL 0
#define EOF (-1)
#define BUFSIZ 1024
#define OPEN_MAX  20   /* 最多打开文件数 */
typedef struct _iobuf {
    int cnt;     /*未读写字节数 */
    char *ptr;   /*下一可读写位置 */
    char *base;  /* 起始位置 */
    int flag;    /* 存取模式 */ 
    int fd;      /*文件描述符 */
}FILE;             
extern  FILE  _iob[OPEN_MAX];      //用数组实现文件缓冲
#define stdin (&_iob[0]) 
#define stdout (&_iob[1])  
#define stderr (&_iob[2])
 
FILE _iob[OPEN_MAX]={
{ 0, ( char * ) 0, ( char * ) 0, _READ, 0 },      //标准输入
{ 0, ( char * ) 0, ( char * ) 0, _WRITE, 1 },     //标准输出
{ 0, ( char * ) 0, ( char * ) 0, _WRITE | _UNBUF, 2 },   //标准错误 
};
 
enum  _flags {
    _READ= 01,    /* file open for reading */
    _WRITE= 02,   /* file open for writing */
    _UNBUF= 04,  /* file is unbuffered */
    _EOF= 010,    /* EOF has occurred on this file */
    _ERR= 020     /* error occurred on this file */
};
stdout和stderr都用于标准输出(显示器),但是,stderr存储模式为_WRITE | _UNBUF而stdout存储模式为 _WRITE
因此stdout有缓冲:遇到换行符\n或缓冲满(BUFSIZE=1024)才写文件!
#include <stdio.h> 
int main(){
    fprintf(stdout, “hello "); 
    fprintf(stderr, “world!");     
    return 0;
};
输出结果为:world!hello 
#include <stdio.h> 
int main(){
    fprintf(stdout, “hello ");     
    fprintf(stderr, “world!\n");     
    return 0;
};
输出结果为:world!
                     hello
#include <stdio.h>
int main(){
    printf(stdout, “hello \n"); 
    fprintf(stderr, “world!"); 
    return 0;
};
输出结果为:hello
                     world!
二者都可重定位到普通文件中!
#include <stdio.h> 
void main(){
    fprintf(stdout, "from stdout\n"); 
    fprintf(stderr, "from stderr\n”);
};
./hello > out.txt:stdout送out.txt, stderr送屏幕
./hello 2 > err.txt:stdout送屏幕, stderr送err.txt 
./hello > out.txt 2> err.txt:stdout送out.txt,stderr送err.txt 
./hello > combine.txt 2>&1:stdout和stderr都送combine.txt 
./hello > combine.txt 2> combine.txt:stdout和stderr都送combine.txt
  
从文件fp中读数据时,FILE中定义的缓冲区为 输入流缓冲(在内存)
首先要从文件fp中读入1024(缓冲大小BUFSIZ=1024)个字节数据到缓存,然后,再按需从缓存中读取1个(如getc)或n个(如fread)字节并返回
向文件fp中写数据时,FILE中定义的缓冲区为 输出流缓冲
先按需不断地向缓存写1个(如putc)或n个(如fwrite)字节,遇到换行符\n或缓存被写满1024(缓冲大小BUFSIZ=1024)个字节,则将缓存内容一次写入文件fp中
 
在stdio.h中,还有feof()、ferror()、fileno()、getc()、putc()、getchar()、putchar()等宏定义。
系统级I/O函数对文件的标识是文件描述符,C标准I/O库函数中对文件的标识是指向FILE结构的指针,FILE中定义了1024字节的流缓冲区。 
使用流缓冲区可使文件内容缓存在用户缓冲区中,而不是每次都直接读/写文件,从而减少执行系统调用次数。系统调用开销很大
int _fillbuf( FILE *);  /*第一次调用getc(),需用_fillbuf()填充缓冲区*/
int _flushbuf( int, FILE *); /*遇换行或写缓冲区满,调用其将缓冲内容写文件*/
#define feof(p) (((p) ->flag & _EOF) != 0) 
#define ferror(p) (((p) ->flag & _ERR) != 0) 
#define fileno(p) ((p) ->fd)
#define getc(p) (--(p)->cnt>=0?(unsigned char)*(p)->ptr++:_fillbuf(p))  /*cnt是未读字符数*/
#define putc(x,p) (--(p)->cnt>=0?*(p)->ptr++=(x):_flushbuf((x),p))      /*cnt是未写字符数*/
#define getchar() getc(stdin)  
#define putchar(x) putc((x), stdout)
_fillbuf()函数的实现
#include “syscalls.h”
/* _fillbuf: allocate and fill input buffer */
int _fillbuf(FILE *fp){
    int bufsize;
    if ((fp->flag & ( _READ | _EOF | _ERR)) != _READ)
        return EOF;
    bufsize=(fp->flag & _UNBUF)?1:BUFSIZ;  //stderr没有缓冲即bufsize=1
    if ((fp -> base == NULL) /* 刚开始,还没有申请缓冲 */
        if (( fp -> base = (char *) malloc(bufsize)) == NULL) 
            return EOF; /* 缓冲没有申请到 */
    fp->ptr=fp->base;
    fp->cnt=read(fp->fd,fp->ptr,bufsize);    /* cnt<=1024,调用系统调用封装函数进行读文件操作,一次将输入缓冲读满*/    
    if (--fp->cnt < 0) {     /* cnt<=0 */
        if (fp->cnt == -1) fp->flag | = _EOF; 
        else fp->flag | = _ERR;
        fp -> cnt =0;
        return EOF;
    } /*一次将输入缓冲读满*/
    return (unsigned char)*fp->ptr++;   /* 0<cnt<=1023,返回缓冲区当前字节,并ptr加1*/
}
_flushbuf()函数的实现
int _flushbuf(int x, FILE *fp){
    unsigned nc;
    int bufsize;
    if (fp < _iob || fp > _iob + OPEN_MAX)
        return EOF;
    if ((fp->flag & (_WRITE | _ERR)) != _WRITE)
        return EOF;
    bufsize=(fp->flag & _UNBUF) ? 1 : BUFSIZ;
    if (fp->base == NULL) {   /* 刚开始,还没有申请缓冲 */
        if ((fp->base = (char *)malloc(bufsize)) == NULL) {
            fp->flag |= _ERR; 
            return EOF;
        }
    } 
    else {    /* 已存在缓冲,且遇到换行符或缓冲已满 */
        nc = fp->ptr-fp->base;
        if (write(fp->fd,fp->base,nc)!=nc) {
            fp->flag |= _ERR; 
            return EOF
        }
    }
    fp->ptr=fp->base;   /* 缓冲区已空 */
    *fp->ptr++=x;
    fp->cnt=bufsize-1; 
    return x;
}
文件复制的两种方法比较:
/* 方式一: getc、putc版本 */
void filecopy(FILE *infp, FILE *outfp){
    int c;
    while ((c=getc(infp)) != EOF)
        putc(c, outfp);
} 
/* 方式二: read、write版本 */
void filecopy(FILE *infp, FILE *outfp){
    char c;
    while (read(infp->fd,&c,1) != 0)
        write(outfp->fd,&c,1);
}
方式一通过_fillbuf()和_flushbuf()这两个函数一次性读写1024个字节。若文件长度为n,则执行系统调用的次数约为n/512。
对于方式二,若文件长度为n,则需执行2n次系统调用。

猜你喜欢

转载自www.cnblogs.com/yangyuliufeng/p/9360180.html