接前文。
再來看看getc和ungetc的實現。在看這兩個函數的實現之前,我們先來想一想這兩個函數分別需要做的工作。
int getc(FILE *stream)
說明:函數getc從stream指向的輸入流中讀取下一個字符(如果有的話),並把它由unsigned char類型轉換為int類型,並且流的相關的文件定位符(如果定義的話)向前移動一位。
返回值:函數getc返回stream指向的輸入流的下一個字符,如果流處於文件結束處,則設置該流的文件結束指示符,函數getc返回EOF。如果發生了讀錯誤,則設置流的錯誤指示符。函數getc返回EOF。
int ungetc( int c , FILE *stream );
說明:
函數ungetc把c指定的字符(轉換為unsigned char類型)退回到stream指向的輸入流中,退回字符是通過對流的後續讀取並按照退回的反順序來返回的。如果中間成功調用(對同一個流)了一個文件定位函數(fseek、fsetpos或者rewind),那麼就會丟棄流的所有退回的字符。流的相應的外部存儲保持不變。
退回的一個字符是受到保護的。如果同一個流調用ungetc函數太多次,中間又沒有讀或者文件定位操作,那麼這種操作可能會失敗。
如果c的值和宏EOF的值相等,則操作失敗且輸入流保持不變。
對ungetc的成功調用會清空流的文件結束符。讀取或者丟棄所有回退的字符後,流的文件定位符的值和這些字符回退之前的值相同。對文本流來說,在對函數ungetc的一次成功調用之後,它的文件定位符的值是不明確的,直到所有回退字符被讀取或者丟棄為止。對二進制流來說,每成功調用一次ungetc之後它的文件定位符都會減1。如果在一次調用之前它的值是零,那麼調用之後它的值是不確定的。
返回值:
函數ungetc返回轉換後的回退字符,如果操作失敗,則返回EOF。
大致來說,getc就是從stream中讀入一個字符,並將流的定位符前移;ungetc就是將流的定位符後移,然後將一個字符回退到stream流該定位符的位置上。
這時我們可能會考慮到一些問題:
1.在stdio.h閱讀筆記1中,我們看到在fopen函數調用之後,並沒有為我們的stream分配buffer,那麼這個分配會在真正進行stream讀取的時候建立嗎?
2.如果在getc之前(即流定位符在_base)就進行ungetc操作,會產生什麼樣的效果呢?
3.如原文所說:“如果同一個流調用ungetc函數太多次,中間又沒有讀或者文件定位操作,那麼這種操作可能會失敗。”,是因為文件定位符到達_base的原因嗎?
那我們就來看下getc和ungetc所做的工作:
首先來看ungetc
**!== == EOF) || !->_flag & _IOREAD) ||->_flag & _IORW) && !(stream->_flag & (stream->_base == (stream->_ptr == stream-> (stream-> ->_ptr++ (stream->_flag & (*--stream->_ptr != (++stream-> *--stream->_ptr = (->_cnt++->_flag &= ~->_flag |= _IOREAD; ( &
清楚的看到,對於沒有分配buffer的I/O控制塊,進行了緩沖區的分配。另外,當文件定位符指向文件開始時且退回字符數目為0時,文件定位符無需後移
看了下_getbuf函數的實現,從中可以看到FILE類中_charbuf變量的作用。刪除了針對不同各個平台的#if代碼之後,_getbuf函數核心代碼如下:
FILE * REG1 FILE * _ASSERTE(str != stream = (stream->_base = stream->_flag |= stream->_bufsiz = stream->_flag |= stream->_base = ( *)&(stream-> stream->_bufsiz = stream->_ptr = stream-> stream->_cnt = }
可以看到,當_getbuf分配不成功時,_base指針會指向_charbuf作為一個1 int長度的空間(可能是因為平台的原因,這裡將1int理解為2個char),此時的_buffersize就為2。通過_charbuf的使用,也保證了當buffer分配失敗時,也能正常從文件中讀取數據。
接下來看下getc函數的實現:
getc(_stream) (--(_stream)->_cnt >= 0 \ ? & *(_stream)->_ptr++ : _filbuf(_stream))
表達式 (--(_stream)->_cnt >= 0 ? 0xff & *(_stream)->_ptr++ : _filbuf(_stream)) 的意義為:
當回退字符的數目大於0的時候,減少一個剩余字符計數,返回0xff & *(_stream)->_ptr++;
否則,調用_filbuf(_stream)。
通過0xff & *(_stream)->_ptr++,getc函數獲得了當前文件定位符指向的內容(只取8位?難道char還能不止8位嗎?),然後文件定位符前移
剩下函數_filbuf(_stream),顯然其中含有C語言從文件中獲取文本的方法(前面都是文本的管理機制):
FILE * REG1 FILE * _ASSERTE(str != stream = (!inuse(stream) || stream->_flag & (stream->_flag & stream->_flag |= stream->_flag |= (! stream->_ptr = stream-> stream->_cnt = _read(_fileno(stream), stream->_base, stream-> ((stream->_cnt == ) || (stream->_cnt == - stream->_flag |= stream->_cnt ? stream->_cnt = ( !(stream->_flag & (_IOWRT|_IORW)) && ((_osfile_safe(_fileno(stream)) & (FTEXT|FEOFLAG)) == (FTEXT| stream->_flag |= ( (stream->_bufsiz == _SMALL_BUFSIZ) && (stream->_flag & _IOMYBUF) && !(stream->_flag & stream->_bufsiz = stream->_cnt-- ( & *stream->_ptr++ }
在語句stream->_cnt = _read(_fileno(stream), stream->_base, stream->_bufsiz);中通過調用_read函數來將原來在硬盤文件中的數據讀取到程序數據段的棧空間中,具體的功能看下_read的代碼:
* bytes_read; *buffer; os_read; *p, *q; peekchr; ULONG filepos; ULONG dosretval; ( ((unsigned)fh >= (unsigned)_nhandle) || !(_osfile(fh) & errno = _doserrno = ; - bytes_read = ; buffer = (cnt == || (_osfile(fh) & ((_osfile(fh) & (FPIPE|FDEV)) && _pipech(fh) != *buffer++ = ++ -- _pipech(fh) = LF; ( !ReadFile( (HANDLE)_osfhnd(fh), buffer, cnt, (LPDWORD)& ( (dosretval = GetLastError()) == errno = _doserrno = - ( dosretval == - bytes_read += os_read; (_osfile(fh) & ( (os_read != ) && (*( *)buf == _osfile(fh) |= _osfile(fh) &= ~ p = q = (p < ( *)buf + (*p == ( !(_osfile(fh) & _osfile(fh) |= ; (*p != *q++ = *p++ (p < ( *)buf + bytes_read - (*(p+) == p += *q++ = LF; *q++ = *p++; ++ dosretval = ( !ReadFile( (HANDLE)_osfhnd(fh), &peekchr, (LPDWORD)& dosretval = (dosretval != || os_read == *q++ = (_osfile(fh) & (FDEV| (peekchr == *q++ = *q++ = _pipech(fh) = (q == buf && peekchr == *q++ = filepos = _lseek_lk(fh, - (peekchr != *q++ = bytes_read = q - ( * bytes_read; }
通過調用ReadFile的WINAPI函數,就可以完成從磁盤中把文件的內容讀到數據段的棧內了。
通過使用已經分配好的文件句柄,將數據讀到lpBuffer指向的內存區域,讀入的最大數據量通過nNumberOfBytesToRead變量進行設定,通過lpNumberOfBytesRead返回實際讀取的字節數。
_read函數在讀到數據之後,如果設定的模式為文本流,則需要解決文本流中的換行回車的問題,這裡就不深究了。有興趣可以看文章:
http://www.360doc.com/content/11/0113/20/3508740_86319358.shtml
這樣,我們的getc就成功將FILE類的_cnt設定為了剩余字符的數目,將_base指向了通過棧上存儲數據的地址。然後,只要將當前文件位置指針_ptr中的數據返回,_ptr指向下一個位置,_cnt減一就好了。
這時候,我們回過頭來看FILE結構,就可以知道裡面成員變量的意義了。
typedef * * * }FILE;
_ptr為數據存儲區域(可為緩沖區或_charbuf對應的存儲區域)當前位置指針。
_cnt為數據存儲區域剩余字符數目。
_base為數據存儲區域首地址。
_flag為當前I/O控制塊所控制的I/O操作類型,有讀、寫等。
_file為對應的文件描述符(詳見博文《走進C標准庫(2)》)
_charbuf為緩沖區申請失敗時的一個2字節的數據存儲區
_bufsiz為緩沖區大小
_tmpfname為所使用存儲一些中間操作數據的臨時文件。
到這裡,我們就可以回答開始時的3個問題了。
1.在getc和ungetc中如果沒有分配buffer,都會嘗試給I/O控制塊分配buffer,但是ungetc分配完buffer後不會嘗試從文件中將數據讀到buffer中。
2.可以在buffer的首地址處存儲一個字符。如果_ptr指在_base,那麼數據存儲區域為空(為從文件中讀取數據)時,ungetc才能在首地址處添加一個字符。
3.多次ungetc使_ptr到達_base之後,就無法再進行ungetc了。
完