C语言getchar和putchar是怎么实现的?

所有的stdio库函数,比如包括getchar/putchar/getc/putc之类的,都会经过stdio buffer:

如果从最简单的实现来说,可以把FILE结构体定义成下面这样 :

typedef struct _iobuf{
	int fd;			//文件描述符
	int cnt;		//缓冲区剩余字节数
	char *base;		//缓冲区地址
	char *ptr;		//缓冲区下一个字符地址
	int flag;		//访问模式
} FILE;   

里面有5个参数,分别记录了:

  • fd :系统调用open返回的文件描述符。
  • base、ptr : 缓冲区地址、缓冲区下一个字符地址,上图标示了。
  • cnt : 缓冲区剩余字符数,当cnt为0时,会系统调用read来填满缓冲区。
  • flag : 文件访问模式,记录了文件打开方式、是否到达文件结尾等 。其中有一个字段,标识不进行缓冲,说明此种情况下每一次读取和输出都调用系统函数,可以使用setvbuf设置。一个例子就是标准错误流stderr : 当stderr连接的是终端设备时,写入一个字符就立即在终端设备显示。
enum _flags {
	_READ = 1,		
	_WRITE = 2,	
	_UNBUF = 4,	 //不进行缓冲
	_EOF = 8,		
	_ERR = 16
};

 

题主问的getchar可以这么实现:

int getc(FILE *f){
	return --f->cnt >= 0 ? *f->ptr++ : fillbuf(f);
}

int getchar(){
  return getc(stdin);
}

当缓冲区中还有剩余字符待读取时,即cnt>0,读取该字符并返回,并将缓冲区指针向后移动一个char单位,否则就调用fillbuf函数填满缓冲区,fillbuf的返回值就是待读取的字符。

fillbuf通过系统调用read读取一定字节的数据填满缓冲区:

int fillbuf(FILE *f){

	int bufsize;

        if((f->flag & (_READ | _EOF | _ERR)) != _READ){     //判断文件是否可读
		return EOF;
	}

	bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;

	if(f->base == NULL){			//没有分配过缓冲区
		if((f->base = (char *)malloc(bufsize)) == NULL){
			return EOF;
		}
	}

	f->ptr = f->base;
        int n = read(f->fd,f->ptr,BUFSIZ);      //系统调用read
        if(n == 0){         //到达文件结尾
		f->base = NULL;
		f->cnt = 0;
		f-> flag |= _EOF;
		return EOF;
        }else if(n == -1){      //出错
                f->cnt= 0;
		f->flag |= _ERR;
                return EOF;
	}else{
		f->cnt = --n;
		return *f->ptr++;	
	}
}
  • 判断文件是否可读。
  • 判断调用read函数时应该读取的字节数,当文件设置了无缓冲时,读取1个字节,否则读取BUFSIZ个字节,BUFSIZ在<stdlib.h>中定义,定义了在该操作系统条件下缓冲区的最佳大小。
  • 判断是否分配过缓冲区(fopen不会分配缓冲区,会再第一次调用getc时分配)。
  • 调用系统函数read。
  • 判断read返回值,分为到达文件结尾、出错和正常读取三种情况。
  • 正常情况下返回缓冲区第一个字符给getc函数,并将cnt减1。

大概就是这样。

 

putchar也类似的实现:

int putc(int x,FILE *f){
	return --f->cnt >= 0 ? *f->ptr++ = x : flushbuf(x,f);
}

int putchar(int x){
	return putc(x, stdout);
}

将写入的字符放到ptr指向的位置,并将ptr向后移动一位。

当缓冲区满时,调用flushbuf将缓冲区内容刷新到文件中。实现一些flushbuf :

int flushbuf(int x,FILE *f){

	if((f->flag & (_WRITE | _EOF | _ERR)) != _WRITE){     //判断文件是否可写
		return EOF;
	}

	int n;
	int bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;
	if(f->base != NULL){
		n = write(f->fd,f->base,f->ptr - f->base);      //判断需要写入多少字节
		if(n != f->ptr - f->base){
			f->flag |= _ERR;
			return EOF;
		}
	}else{
		if((f->base = (char *)malloc(bufsize)) == NULL){
			f->flag |= _ERR;
			return EOF;
		}
	}

	if(x != EOF){
		f->cnt = bufsize - 1;
		f->ptr = f->base;
		*f->ptr++ = x;
	}else{          //当写入EOF时,代表强制刷新缓冲区内容到文件中
		f->cnt = bufsize;
		f->ptr = f->base;
	}
	return x;
}
  • 判断文件是否可写。
  • 当已分配过缓冲区时,将缓冲区的内容通过系统调用write写入文件中。写入的字节数为f->ptr - f->base
  • 当没有分配过缓冲区时,分配缓冲区。
  • 判断当写入的字符为EOF时,说明调用此函数的目的为强制刷新缓冲区,不写入字符。将cnt赋值为BUFSIZ,ptr赋值为缓冲区首地址base。
  • 当写入字符不为EOF时,说明缓冲区已满,需要将缓冲区刷新到文件中。cnt为BUFSIZE - 1,将写入的字符x放到到缓冲区的第一格,然后将ptr向后移动一个char单位。

 

至于不通过getchar访问缓冲区,意思是想看buffer里的内容吗?

可以用setvbuf自己设置buffer。这段代码来自how does work setvbuf() in C [closed]

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int file_size;
    char buffer[BUFSIZ];
    FILE * fp = fopen("test.txt","w+");
    if (setvbuf(fp,buffer,_IOFBF,BUFSIZ) != 0)
    {
       perror("setvbuf()");
       fprintf(stderr,"setvbuf() failed in file %s at line # %d\n", __FILE__,__LINE__-3);
       exit(EXIT_FAILURE);
    }
 
    /* Exhibit the contents of buffer. */
    fputs ("aaa",fp);
    printf("%s\n", buffer);
    fputs ("bbb",fp);
    printf("%s\n", buffer);
    fputs ("ccc",fp);
    printf("%s\n", buffer);
    file_size = ftell(fp);
    printf("file_size = %d\n", file_size);

    fflush (fp);              /* flush buffer */

    printf("%s\n", buffer);
    fputs ("ddd",fp);
    printf("%s\n", buffer);
    fputs ("eee",fp);
    printf("%s\n", buffer);
 
    rewind(fp);               /* flush buffer and rewind file */
    char buf[20];
    fgets(buf,sizeof buf,fp);
    printf("%s\n", buf);
 
    fclose(fp);
 
    return 0;
}

输出显示的就是buffer里的内容:

aaa
aaabbb
aaabbbccc
file_size = 9
aaabbbccc
dddbbbccc
dddeeeccc
aaabbbcccdddeee
posted @ 2022-11-16 11:15  arthurzyc  阅读(226)  评论(0编辑  收藏  举报