標準I/O庫的緩沖機制
標準I/O庫提供緩沖的目的是盡可能減少使用read和write調用的次數。它也對每個I/O流自動地進行緩沖管理,從而避免了應用程序需要考慮這一點所帶來的麻煩。
標準I/O提供了三種類型的緩沖:
(1)全緩沖。這種情況下,在填滿標準I/O緩沖區后才進行實際的I/O操作。對于駐留在磁盤上的文件通常是由標準I/O庫實施全緩沖的。在一個流上執行第一次I/O操作時,相關標準I/O函數通常調用malloc獲得需使用的緩沖區。
術語沖洗(flush)說明標準I/O緩沖區的寫操作。緩沖區可由標準I/O例程自動沖洗(例如當填滿一個緩沖區時),或者可以調用函數fflush沖洗一個流。值得引起注意的是在UNIX環境中,flush有兩種意思:在標準I/O庫方面,flush(沖洗)意味著將緩沖區中的內容寫到磁盤上(該緩沖區可能只是局部填寫的)。在終端驅動程序方面,flush(刷清)表示丟棄已存儲在緩沖區中的數據。
(2)行緩沖。在這種情況下,當在輸入和輸出中遇到換行符時,標準I/O庫執行I/O操作。這允許我們一次輸出一個字符(用標準I/O fputc函數,該函數一次只輸出一個字符,沒有換行符),但只有在寫了一行之后才進行實際I/O操作。當流涉及一個終端時(例如標準輸入和標準輸出),通常使用行緩沖。
對于行緩沖有兩個限制。第一,因為標準I/O庫用來收集每一行的緩沖區的長度是固定的,所以只要填滿了緩沖區,那么即使還沒有寫一個換行符,也進行 I/O操作。第二,任何時候只要通過標準I/O庫要求從(a)一個不帶緩沖的流,或者(b)一個行緩沖的流(它要求從內核得到數據)得到輸入數據,那么就 會造成沖洗所有行緩沖輸出流。在(b)中帶了一個括號中的說明,其理由是,所需的數據可能已在該緩沖區中,它并不要求在需要數據時才從內核讀數據。很明 顯,從不帶緩沖的一個流中進行輸入((a)項)要求當時從內核得到數據。
(3)不帶緩沖。標準I/O庫不對字符進行緩沖存儲。例如,如果用標準I/O函數fputs寫15個字符到不帶緩沖的流中,則該函數很可能用write系統調用函數將這些字符立即寫到相關聯的打開文件上。
標準出錯流stderr通常是不帶緩沖的,這就使得出錯信息可以盡快顯示出來,而不管它們是否含有一個換行符。
ISO C要求下列緩沖特征:
- 當且僅當標準輸入和標準輸出并不涉及交互式設備時,它們才是全緩沖的。
- 標準出錯絕不會是全緩沖的。
但是,這并沒有告訴我們如果標準輸入和輸出涉及交互式設備時,它們是不帶緩沖的還是行緩沖的;以及標準出錯是不帶緩沖的還是行緩沖的。很多系統默認使用下列類型的緩沖:
- 標準出錯是不帶緩沖的。
- 如若是涉及終端設備的其他流,則它們是行緩沖的;否則是全緩沖的。
對任何一個給定的流,如果我們并不喜歡這些系統默認的情況,則可調用下列兩個函數中的一個更改緩沖類型:
#include <stdio.h> void setbuf( FILE *restrict fp, char *restrict buf ); int setvbuf( FILE *restrict fp, char *restrict buf, int mode, size_t size ); 返回值:若成功則返回0,若出錯則返回非0值
這些函數一定要在流已被打開后調用,而且也應該在對該流執行任何一個其他操作之前調用。
可以使用setbuf函數打開或關閉緩沖機制。為了帶緩沖進行I/O,參數buf必須指向一個長度為BUFSIZE的緩沖區(該常量定義在
使用setvbuf,我們可以精確地指定所需的緩沖類型。這是用mode參數實現的:
_IOFBF | 全緩沖 |
_IOLBF | 行緩沖 |
_IONBF | 不帶緩沖 |
如果指定一個不帶緩沖的流,則忽略buf和size參數。如果指定全緩沖或行緩沖,則buf和size可選擇地指定一個緩沖區及其長度。如果該流是 帶緩沖的,而buf是NULL,則標準I/O庫將自動地為該流分配適當長度的緩沖區。適當長度指的是由常量BUFSIZE所指定的值。
某些C函數庫實現使用stat結構中的成員st_blksize所指定的值決定最佳I/O緩沖區長度。GNU C函數庫就使用這種方法。
表5-1列出了這兩個函數的動作,以及它們的各個選項。
要了解,如果在一個函數內分配一個自動變量類的標準I/O緩沖區,則從該函數返回之前,必須關閉該流。另外,有些實現將緩沖區的一部分用于存放它自 己的管理操作信息,所以可以存放在緩沖區中的實際數據字節數少于size。一般而言,應由系統選擇緩沖區的長度,并自動分配緩沖區。在這種情況下關閉此流 時,標準I/O庫將自動釋放緩沖區。
任何時候,我們都可以強制沖洗一個流。
#include <stdio.h> int fflush( FILE *fp ); 返回值:若成功則返回0,若出錯則返回EOF
此函數使該流所有未寫的數據都被傳送至內核。作為一個特例,如若fp是NULL,則此函數將導致所有輸出流被沖洗。