溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

發(fā)布時(shí)間:2020-09-18 14:54:25 來(lái)源:腳本之家 閱讀:179 作者:張雅宸 欄目:編程語(yǔ)言

寫(xiě)一下fopen/getc/putc等C庫(kù)的粗略實(shí)現(xiàn),參考了K&R,但是有幾點(diǎn)根據(jù)自己理解的小改動(dòng),下面再具體說(shuō)一下^_^

寫(xiě)這篇文章主要是幫助自己理解下標(biāo)準(zhǔn)I/O庫(kù)大體是怎么工作的。

fopen與open之間的關(guān)系

操作系統(tǒng)提供的接口即為系統(tǒng)調(diào)用。而C語(yǔ)言為了讓用戶(hù)更加方便的編程,自己封裝了一些函數(shù),組成了C庫(kù)。而且不同的操作系統(tǒng)對(duì)同一個(gè)功能提供的系統(tǒng)調(diào)用可能不同,在不同的操作系統(tǒng)上C庫(kù)對(duì)用戶(hù)屏蔽了這些不同,所謂一次編譯處處運(yùn)行。這里open為系統(tǒng)調(diào)用,fopen為C庫(kù)提供的調(diào)用。

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

C庫(kù)對(duì)的讀寫(xiě)操作封裝了一個(gè)緩沖區(qū)。試想假如用戶(hù)頻繁的對(duì)文件讀寫(xiě)少量字符,會(huì)頻繁的進(jìn)行系統(tǒng)調(diào)用(read函數(shù)),而系統(tǒng)調(diào)用比較耗時(shí)。C庫(kù)自己封裝了一個(gè)緩沖區(qū),每次讀取特定數(shù)據(jù)量到緩沖區(qū),讀取時(shí)優(yōu)先從緩沖區(qū)讀取,當(dāng)緩沖區(qū)內(nèi)容被讀光后才進(jìn)行系統(tǒng)調(diào)用將緩沖區(qū)再次填滿(mǎn)。

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

FILE結(jié)構(gòu)體

上面我們看到一個(gè)結(jié)構(gòu)體,里面有5個(gè)參數(shù),分別記錄了:緩沖區(qū)剩余的字符數(shù)cnt、下一個(gè)字符的位置ptr、緩沖區(qū)的位置base、文件訪(fǎng)問(wèn)模式flag、文件描述符fd。

其中文件描述符就是系統(tǒng)調(diào)用open返回的文件描述符fd,是int類(lèi)型。ptr與base上面圖中已經(jīng)展示了。cnt是緩沖區(qū)剩余字符數(shù),當(dāng)cnt為0時(shí),會(huì)系統(tǒng)調(diào)用read來(lái)填滿(mǎn)緩沖區(qū)。flag為文件訪(fǎng)問(wèn)模式,記錄了文件打開(kāi)方式、是否到達(dá)文件結(jié)尾等。

結(jié)構(gòu)體的具體定義如下,對(duì)應(yīng)調(diào)用fopen返回的文件指針FILE *fp = fopen(xxx,r):

typedef struct _iobuf{
 int cnt; //緩沖區(qū)剩余字節(jié)數(shù)
 char *base; //緩沖區(qū)地址
 char *ptr; //緩沖區(qū)下一個(gè)字符地址
 int fd; //文件描述符
 int flag; //訪(fǎng)問(wèn)模式
} FILE; //別名,與標(biāo)準(zhǔn)庫(kù)一致

結(jié)構(gòu)體中有flag字段,flag字段可以是以下幾種的并集:

enum _flags {
 _READ = 1, 
 _WRITE = 2, 
 _UNBUF = 4, //不進(jìn)行緩沖
 _EOF = 8, 
 _ERR = 16
};

我們注意到其中有一個(gè)字段,標(biāo)識(shí)不進(jìn)行緩沖,說(shuō)明此種情況下每一次讀取和輸出都調(diào)用系統(tǒng)函數(shù)。一個(gè)例子就是標(biāo)準(zhǔn)錯(cuò)誤流stderr : 當(dāng)stderr連接的是終端設(shè)備時(shí),寫(xiě)入一個(gè)字符就立即在終端設(shè)備顯示。

而stdin和stdout都是帶緩沖的,明確的說(shuō)是行緩沖。本文不考慮行緩沖,默認(rèn)都是全緩沖,即緩沖區(qū)滿(mǎn)了才刷新緩沖區(qū)。(詳細(xì)可以參考《UNIX環(huán)境高級(jí)編程》標(biāo)準(zhǔn)I/O庫(kù)章節(jié))。

現(xiàn)在我們可以初始化stdin、stdout與stderr:

FILE _iob[OPEN_MAX] = {
 {0,NULL,NULL,_READ,0},
 {0,NULL,NULL,_WRITE,1},
 {0,NULL,NULL,_WRITE|_UNBUF,2}
};

_ferror/_feof/_fileno

//判斷文件流中是否有錯(cuò)誤發(fā)生
int _ferror(FILE *f){
 return f-> flag & _ERR;
}
//判斷文件流是否到達(dá)文件尾
int _feof(FILE *f){
 return f-> flag & _EOF;
}
//返回文件句柄,即open函數(shù)的返回值
int _fileno(FILE *f){
 return f->fd;
}

_fopen

FILE *_fopen(char *file,char *mode){
 int fd;
 FILE *fp; 
 if(*mode != 'r' && *mode != 'w' && *mode != 'a') {
 return NULL;
 } 
 for(fp = _iob; fp < _iob + OPEN_MAX; fp++) { //尋找一個(gè)空閑位置
 if (fp->flag == 0){ 
 break;
 }
 }
 if(fp >= _iob + OPEN_MAX){
 return NULL;
 }
 if(*mode == 'w'){
 fd = creat(file,PERMS);
 }else if(*mode == 'r'){
 fd = open(file,O_RDONLY,0);
 }else{ //a模式
 if((fd = open(file,O_WRONLY,0)) == -1){
 fd = creat(file,PERMS);
 }
 lseek(fd,0L,2); //文件指針指向末尾
 }
 if(fd == -1){
 return NULL;
 }
 fp->fd = fd;
 fp->cnt = 0; //fopen不分配緩存空間
 fp->base = NULL;
 fp->ptr = NULL;
 fp->flag = *mode == 'r' ? _READ : _WRITE;
 return fp;
}

fopen的處理過(guò)程:

判斷打開(kāi)模式的合法性。

在_iob中尋找一個(gè)空閑位置,找不到的話(huà)說(shuō)明程序打開(kāi)的文件數(shù)已經(jīng)到達(dá)的最大值,不能再打開(kāi)新的文件。

如果是w模式,創(chuàng)建一個(gè)新文件。如果是r模式,以只讀方式打開(kāi)文件。如果是a模式,首先打開(kāi)文件,如果打開(kāi)失敗則創(chuàng)建文件,否則通過(guò)系統(tǒng)調(diào)用lseek將文件指針置到末尾。

對(duì)FILE結(jié)構(gòu)體進(jìn)行初始化,注意fopen不會(huì)分配緩沖區(qū)。

_getc

getc的作用是從文件中返回下一個(gè)字符,參數(shù)是文件指針,即FILE:

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

對(duì)照上面的圖示:當(dāng)緩沖區(qū)中還有剩余字符待讀取時(shí),讀取該字符并返回,并將緩沖區(qū)指針向后移動(dòng)一個(gè)char單位,否則就調(diào)用_fillbuf函數(shù)填滿(mǎn)緩沖區(qū),_fillbuf的返回值就是待讀取的字符。

這里有一個(gè)問(wèn)題:當(dāng)讀取到最后一個(gè)字符時(shí),cnt為0,但是ptr已經(jīng)越界了,如下圖:

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

這種情況雖然是非法的,但是C語(yǔ)言中保證:數(shù)組末尾之后的第一個(gè)元素(即&arr[n],或者arr + n)的指針?biāo)阈g(shù)運(yùn)算可以正確執(zhí)行。下面的例子也是上述的一種應(yīng)用場(chǎng)景:

int a[10] = {1,2,3,4,5,6,7,8,9,10};
for(int *x = a;x < a + 10; x++){
 printf("%d\n",*x);
}

當(dāng)for循環(huán)到最后一步時(shí),x也指向了a[10],雖然是非法的,但是C語(yǔ)言保證可以正確執(zhí)行,只要不出現(xiàn)如下情況就ok:

int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *x;
for(x = a;x < a + 10; x++){
 printf("%d\n",*x);
}
*x = 11; //越界進(jìn)行值訪(fǎng)問(wèn)

_fillbuf

我們看下_getc中的_fillbuf的實(shí)現(xiàn),_fillbuf是當(dāng)緩沖區(qū)沒(méi)有可以讀取的字符時(shí),通過(guò)系統(tǒng)調(diào)用read讀取一定字節(jié)的數(shù)據(jù)填滿(mǎn)緩沖區(qū),供之后使用:

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){  //沒(méi)有分配過(guò)緩沖區(qū)
 if((f->base = (char *)malloc(bufsize)) == NULL){
  return EOF;
 }
 }
 f->ptr = f->base;
 int n = read(f->fd,f->ptr,BUFSIZ); //系統(tǒng)調(diào)用read
 if(n == 0){  //到達(dá)文件結(jié)尾
 f->base = NULL;
 f->cnt = 0;
 f-> flag |= _EOF;
 return EOF;
 }else if(n == -1){ //出錯(cuò)
  f->cnt= 0;
 f->flag |= _ERR;
  return EOF;
 }else{
 f->cnt = --n;
 return *f->ptr++; 
 }
}

_fillbuf的處理過(guò)程:

判斷文件是否可讀

判斷調(diào)用read函數(shù)時(shí)應(yīng)該讀取的字節(jié)數(shù),當(dāng)文件設(shè)置了無(wú)緩沖時(shí),讀取1個(gè)字節(jié),否則讀取BUFSIZ個(gè)字節(jié),BUFSIZ在

判斷是否分配過(guò)緩沖區(qū)(fopen不會(huì)分配緩沖區(qū),會(huì)再第一次調(diào)用getc時(shí)分配)。

調(diào)用系統(tǒng)函數(shù)read。

判斷read返回值,分為到達(dá)文件結(jié)尾、出錯(cuò)和正常讀取三種情況。

正常情況下返回緩沖區(qū)第一個(gè)字符給getc函數(shù),并將cnt減1。

這里注意,到達(dá)文件結(jié)尾和出錯(cuò)都是返回EOF,區(qū)別是前者會(huì)將flag的_EOF位置1,后者會(huì)將flag的_ERR位置1,上游可以通過(guò)feof和ferror函數(shù)進(jìn)行判斷(這兩個(gè)函數(shù)在上面已經(jīng)實(shí)現(xiàn)過(guò)了)。

The character read is returned as an int value.
If the End-of-File is reached or a reading error happens, the function returns EOF and the corresponding error or eof indicator is set. You can use either ferror or feof to determine whether an error happened or the End-Of-File was reached.

_putc

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

與_getc的實(shí)現(xiàn)相似,將寫(xiě)入的字符放到ptr指向的位置,并將ptr向后移動(dòng)一位。當(dāng)緩沖區(qū)滿(mǎn)時(shí),調(diào)用_flushbuf將緩沖區(qū)內(nèi)容刷新到文件中。

_flushbuf

int _flushbuf(int x,FILE *f){
 if((f->flag & (_WRITE | _EOF | _ERR)) != _WRITE){ //判斷文件是否可寫(xiě)
 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); //判斷需要寫(xiě)入多少字節(jié)
 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{  //當(dāng)寫(xiě)入EOF時(shí),代表強(qiáng)制刷新緩沖區(qū)內(nèi)容到文件中
 f->cnt = bufsize;
 f->ptr = f->base;
 }
 return x;
}

_flushbuf的處理過(guò)程:

判斷文件是否可寫(xiě)。

當(dāng)已分配過(guò)緩沖區(qū)時(shí),將緩沖區(qū)的內(nèi)容通過(guò)系統(tǒng)調(diào)用write寫(xiě)入文件中。

當(dāng)沒(méi)有分配過(guò)緩沖區(qū)時(shí),分配緩沖區(qū)。

判斷當(dāng)寫(xiě)入的字符為EOF時(shí),說(shuō)明調(diào)用此函數(shù)的目的為強(qiáng)制刷新緩沖區(qū),不寫(xiě)入字符。將cnt賦值為BUFSIZ,ptr賦值為緩沖區(qū)首地址base。

當(dāng)寫(xiě)入字符不為EOF時(shí),說(shuō)明緩沖區(qū)已滿(mǎn),需要將緩沖區(qū)刷新到文件中。cnt為BUFSIZE - 1,將寫(xiě)入的字符x放到到緩沖區(qū)的第一格,然后將ptr向后移動(dòng)一個(gè)char單位。

注意,調(diào)用write函數(shù)時(shí),寫(xiě)入的字節(jié)數(shù)不能寫(xiě)死為1或者BUFSIZ:

n = write(f->fd,f->base,f->ptr - f->base); //判斷需要寫(xiě)入多少字節(jié)

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

如上圖,我們需要寫(xiě)入base至ptr之間的數(shù)據(jù),而不是BUFSIZ,因?yàn)槲覀兛赡軙?huì)強(qiáng)制刷新緩沖區(qū)而不是等到緩沖區(qū)滿(mǎn)了才刷新緩沖區(qū)。

還有一點(diǎn):當(dāng)我們想要強(qiáng)制刷新緩沖區(qū)時(shí),第一個(gè)參數(shù)x該傳入什么呢?K&R傳遞的是字符0,但是我認(rèn)為這樣會(huì)污染緩沖區(qū),所以我的實(shí)現(xiàn)是傳入一個(gè)特殊字符EOF,根據(jù)EOF來(lái)做不同的處理:

if(x != EOF){
 f->cnt = bufsize - 1;
 f->ptr = f->base;
 *f->ptr++ = x;
}else{  //當(dāng)寫(xiě)入EOF時(shí),代表強(qiáng)制刷新緩沖區(qū)內(nèi)容到文件中
 f->cnt = bufsize;
 f->ptr = f->base;
}

當(dāng)緩沖區(qū)滿(mǎn)時(shí),刷新緩沖區(qū)后緩沖區(qū)的表現(xiàn):

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

當(dāng)強(qiáng)制刷新緩沖區(qū)時(shí),緩沖區(qū)的表現(xiàn):

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

但是按照K&R的方式來(lái)強(qiáng)制刷新緩沖區(qū)時(shí),緩沖區(qū)的表現(xiàn):

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

這樣會(huì)污染緩沖區(qū),所以我的實(shí)現(xiàn)是傳入EOF來(lái)強(qiáng)制刷新緩沖區(qū)。

_fflush

_fflush(FILE *f)的作用是把緩沖區(qū)內(nèi)容寫(xiě)入文件。當(dāng)參數(shù)為空時(shí),會(huì)刷新所有文件:

int _fflush(FILE *f){
 int res = 0;
 if(f == NULL){
 for(int i = 0; i < OPEN_MAX; i++){  //當(dāng)參數(shù)為NULL時(shí),刷新所有的文件流
   if((f->flag & _WRITE) && (_fflush(&_iob[i]) == -1)){ //有一個(gè)出錯(cuò)即返回-1
    res = EOF;
   }
 }
 }else{
  if(f->flag & _WRITE){
   _flushbuf(EOF,f);
  }else{
  res = EOF;
  }
 }
 if(f->flag & _ERR){ //出錯(cuò)
  res = EOF;
 }
 return res;
}

_fflush的處理過(guò)程:

判斷參數(shù)是否為空,如果為空的話(huà),遍歷_iob數(shù)組,將所有文件流都強(qiáng)制刷新。

如果參數(shù)不為空,判斷文件是否可寫(xiě),再調(diào)用_flushbuf進(jìn)行刷新,注意此處傳遞給_flushbuf的參數(shù)是EOF。還有一點(diǎn)就是判斷_flushbuf是否出錯(cuò)不是判斷返回值是否為-1,因?yàn)閰?shù)為EOF時(shí)的返回值也為-1,所以此處用flag & _ERR判斷是否出錯(cuò)。

注意,這里我們只針對(duì)可寫(xiě)的文件流進(jìn)行操作,忽略了只讀的文件流:

If the stream was open for reading, the behavior depends on the specific implementation. In some implementations this causes the input buffer to be cleared.

針對(duì)只讀的文件流,不同系統(tǒng)處理的方式不一樣,有的系統(tǒng)會(huì)清空緩沖區(qū)。

_fclose

int _fclose(FILE *f){
 int ret;
 if((ret = _fflush(f)) != EOF){
  free(f->base);
  f->base = NULL;
  f->ptr = NULL;
  f->fd = 0;
  f->flag = 0;  
    f->cnt=0;
 }
 return 0;
}

fclose調(diào)用fflush函數(shù),保證在文件關(guān)閉前將緩沖區(qū)中的內(nèi)容刷到文件中,并且釋放掉緩沖區(qū)的內(nèi)存空間。

_fseek

關(guān)于fseek的介紹請(qǐng)看fseek

int _fseek(FILE *f,long offset,int origin){
 int rc;
 if(f->flag & _READ) {
  if(origin == 1) {
   offset -= f->cnt;
  }
  rc = lseek(f->fd,offset,origin);
    f->cnt = 0;    //將緩沖區(qū)剩余字符數(shù)清0
 }else if(f->flag & _WRITE) {
    rc = _fflush(f);  //強(qiáng)制刷新緩沖區(qū)
  if(rc != EOF) {
   rc = lseek(f->fd,offset,origin);
  }
 }
 return rc == -1 ? EOF : 0;
}
int _fseek(FILE *f,long offset,int origin){
 int rc;
 if(f->flag & _READ) {
  if(origin == 1) {
   offset -= f->cnt;
  }
  rc = lseek(f->fd,offset,origin);
    f->cnt = 0;    //將緩沖區(qū)剩余字符數(shù)清0
 }else if(f->flag & _WRITE) {
    rc = _fflush(f);  //強(qiáng)制刷新緩沖區(qū)
  if(rc != EOF) {
   rc = lseek(f->fd,offset,origin);
  }
 }
 return rc == -1 ? EOF : 0;
}

當(dāng)文件流為可讀時(shí),見(jiàn)下圖:

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

由于有緩沖區(qū)的存在,我們直覺(jué)上的文件指針位置和真實(shí)的文件指針位置是不同的,差了cnt個(gè)單位長(zhǎng)度。所以當(dāng)我們?cè)O(shè)置移動(dòng)offset個(gè)長(zhǎng)度時(shí),真實(shí)的文件指針需要移動(dòng)offset-cnt個(gè)單位長(zhǎng)度(offset為正數(shù)或者負(fù)數(shù))。

之后我們需要將cnt置為0,以便下次讀取時(shí)將緩沖區(qū)的數(shù)據(jù)更新。

當(dāng)origin為0或者2時(shí),直接調(diào)動(dòng)lseek即可。

而當(dāng)文件流為可寫(xiě)時(shí),見(jiàn)下圖:

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

真實(shí)的文件指針位置與我們直覺(jué)上的文件指針位置差了ptr - base個(gè)單位長(zhǎng)度,即我們新寫(xiě)入緩沖區(qū)的內(nèi)容長(zhǎng)度,所以我們直接調(diào)用_fflush即可。(K&R中直接調(diào)用的write,但是我覺(jué)得這樣沒(méi)有重置ptr指針的位置和cnt,這樣的話(huà)base與ptr之間的內(nèi)容會(huì)被刷入到文件中兩次)。

當(dāng)文件是以a模式打開(kāi)時(shí),fseek無(wú)效:

a+  Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file 
  position for reading is at the beginning of the file, but output is always appended to the end of the file.

_getchar

int _getchar(){
 return _getc(stdin);
}

我們可以發(fā)現(xiàn),_getchar調(diào)用的就是_getc,只不過(guò)_getc可以傳入任意的文件指針,而對(duì)_getchar來(lái)說(shuō),_getc傳入的是stdin,也就是{0,NULL,NULL,_READ,0}。

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

當(dāng)調(diào)用getchar時(shí),首先去stdin結(jié)構(gòu)體中的緩存取數(shù)據(jù),如果緩存為空,會(huì)在_fillbuf中的int n = read(f->fd,f->ptr,BUFSIZ); //系統(tǒng)調(diào)用read處阻塞住,等待用戶(hù)輸入字符。

當(dāng)標(biāo)準(zhǔn)輸入(stdin)連接的是終端時(shí),終端I/O會(huì)采用規(guī)范模式輸入處理:對(duì)于終端輸入以行為單位進(jìn)行處理,對(duì)于每個(gè)讀請(qǐng)求,終端設(shè)備輸入隊(duì)列會(huì)返回一行(用戶(hù)輸入的字符會(huì)緩存在終端輸入隊(duì)列中,直到用戶(hù)輸入一個(gè)行定界符,輸入隊(duì)列中的數(shù)據(jù)會(huì)返回給read函數(shù))。

這一行數(shù)據(jù)會(huì)緩存在標(biāo)準(zhǔn)I/O緩沖區(qū)中,下次調(diào)用getchar時(shí)會(huì)返回緩沖區(qū)第一個(gè)字符。當(dāng)緩沖區(qū)數(shù)據(jù)被讀光時(shí),重復(fù)上述過(guò)程。

_putchar

int _putchar(int x){
 return _putc(x,stdout);
}

我們可以發(fā)現(xiàn),_putchar調(diào)用的就是_putc,只不過(guò)_putc可以傳入任意的文件指針,而對(duì)_putchar來(lái)說(shuō),_putc傳入的是stdout,也就是{0,NULL,NULL,_WRITE,1}。

C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程

調(diào)用putchar時(shí),數(shù)據(jù)會(huì)緩存在stdout中的緩沖中。

當(dāng)stdout的緩沖被裝滿(mǎn)時(shí),會(huì)調(diào)用write將數(shù)據(jù)寫(xiě)入到stdout中,stdout將數(shù)據(jù)寫(xiě)入到終端設(shè)備輸出隊(duì)列中,輸出隊(duì)列將數(shù)據(jù)寫(xiě)入到終端。

完整代碼

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define EOF -1
#define BUFSIZ 1024
#define OPEN_MAX 20  //打開(kāi)的最大文件數(shù)
#define PERMS 0666
typedef struct _iobuf{
 int cnt;  //緩沖區(qū)剩余字節(jié)數(shù)
 char *base;  //緩沖區(qū)地址
 char *ptr;  //緩沖區(qū)下一個(gè)字符地址
 int flag;  //訪(fǎng)問(wèn)模式
  int fd;   //文件描述符
} FILE;  //別名,與標(biāo)準(zhǔn)庫(kù)一致
extern FILE _iob[OPEN_MAX];
//八進(jìn)制
enum _flags {
 _READ = 01,  
 _WRITE = 02, 
 _UNBUF = 04,  //不進(jìn)行緩沖
 _EOF = 010,  
 _ERR = 020 
};
FILE _iob[OPEN_MAX] = {
  {0,NULL,NULL,_READ,STDIN_FILENO},
  {0,NULL,NULL,_WRITE,STDOUT_FILENO},
  {0,NULL,NULL,_WRITE|_UNBUF,STDERR_FILENO}
};
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
int _ferror(FILE *f){
 return f-> flag & _ERR;
}
int _feof(FILE *f){
 return f-> flag & _EOF;
}
int _fileno(FILE *f){
 return f->fd;
}
//返回第一個(gè)字符
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){   //沒(méi)有分配過(guò)緩沖區(qū)
  if((f->base = (char *)malloc(bufsize)) == NULL){
   return EOF;
  }
 }
 f->ptr = f->base;
 int n = read(f->fd,f->ptr,BUFSIZ);  //系統(tǒng)調(diào)用read
 if(n == 0){   //到達(dá)文件結(jié)尾
  f->base = NULL;
  f->cnt = 0;
  f-> flag |= _EOF;
  return EOF;
 }else if(n == -1){  //出錯(cuò)
  f->cnt= 0;
  f->flag |= _ERR;
  return EOF;
 }else{
  f->cnt = --n;
  return *f->ptr++; 
 }
}
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);  //判斷需要寫(xiě)入多少字節(jié)
  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{   //當(dāng)寫(xiě)入EOF時(shí),代表強(qiáng)制刷新緩沖區(qū)內(nèi)容到文件中
  f->cnt = bufsize;
  f->ptr = f->base;
 }
 return x;
}
/**
 * @brief _fflush
 * @param f
 * @return
 */
int _fflush(FILE *f){
  int res = 0;
 if(f == NULL){
    for(int i = 0; i < OPEN_MAX; i++){   //當(dāng)參數(shù)為NULL時(shí),刷新所有的文件流
      if((f->flag & _WRITE) && (_fflush(&_iob[i]) == -1)){ //有一個(gè)出錯(cuò)即返回-1
       res = EOF;
      }
  }
  }else{
   if(f->flag & _WRITE){
     _flushbuf(EOF,f);
   }else{
    res = EOF;
   }
  }
  if(f->flag & _ERR){  //出錯(cuò)
    res = EOF;
 }
  return res;
}
int _fclose(FILE *f){
 int ret;
 if((ret = _fflush(f)) != EOF){
  free(f->base);
  f->base = NULL;
  f->ptr = NULL;
  f->fd = 0;
  f->flag = 0;  //@TODO
 }
 return 0;
}
int _fseek(FILE *f,long offset,int origin){
 int rc;
 if(f->flag & _READ) {
  if(origin == 1) {
   offset -= f->cnt;
  }
  rc = lseek(f->fd,offset,origin);
    f->cnt = 0;    //將緩沖區(qū)剩余字符數(shù)清0
 }else if(f->flag & _WRITE) {
    rc = _fflush(f);  //強(qiáng)制刷新緩沖區(qū)
  if(rc != EOF) {
   rc = lseek(f->fd,offset,origin);
  }
 }
 return rc == -1 ? EOF : 0;
}

int _getc(FILE *f){
 return --f->cnt >= 0 ? *f->ptr++ : _fillbuf(f);
}
int _putc(int x,FILE *f){
 return --f->cnt >= 0 ? *f->ptr++ = x : _flushbuf(x,f);
}
int _getchar(){
 return _getc(stdin);
}
int _putchar(int x){
 return _putc(x,stdout);
}
FILE *_fopen(char *file,char *mode){
 int fd;
 FILE *fp; 
 if(*mode != 'r' && *mode != 'w' && *mode != 'a') {
  return NULL;
 } 
  for(fp = _iob; fp < _iob + OPEN_MAX; fp++) { //尋找一個(gè)空閑位置
    if (fp->flag == 0){
   break;
  }
 }
 if(fp >= _iob + OPEN_MAX){
  return NULL;
 }
 if(*mode == 'w'){
  fd = creat(file,PERMS);
 }else if(*mode == 'r'){
  fd = open(file,O_RDONLY,0);
 }else{  //a模式
  if((fd = open(file,O_WRONLY,0)) == -1){
   fd = creat(file,PERMS);
  }
  lseek(fd,0L,2);  //文件指針指向末尾
 }
 if(fd == -1){
  return NULL;
 }
 fp->fd = fd;
 fp->cnt = 0;  //fopen不分配緩存空間
 fp->base = NULL;
 fp->ptr = NULL;
 fp->flag = *mode == 'r' ? _READ : _WRITE;
 return fp;
}
int main(int argc,char *argv[]){

 FILE *f = _fopen("zyc.txt","a"); 
 /*char c;
  for(int i = 0; i < 10; i++){
  c = _getc(f);
  }*/
 /*for(int i = 0; i < 9; i++){
  _putc('6',f);
  }
  _fseek(f,-5,1);
  for(int i = 0; i < 9; i++){
  _putc('8',f);
  }
  _fclose(f);*/
  int c;
  while((c = _getchar()) != '\n'){
   _putchar(c);   
  }
  _fclose(stdout);
 return 0;
}

上面提到的部分函數(shù)在Answer to Exercise 8-3, page 179中有更詳細(xì)的實(shí)現(xiàn)。

以上這篇C 標(biāo)準(zhǔn)I/O庫(kù)的粗略實(shí)現(xiàn)教程就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI