Linux環境程式設計---標準I/O
前面的章節主要介紹linux的系統呼叫,這一節介紹linux的C庫,標準IO庫有很多細節,如緩衝區分配,優化塊長度執行IO等接下來讓我們來看看C庫中的標準IO
流和FILE物件
在上一章中所有IO都是圍繞檔案描述符進行的,當我們開啟一個檔案的時候,返回它的檔案描述符,而對於標準IO庫,它的操作是圍繞流進行的。當我們用標準IO庫開啟一個檔案的時候,我們已使一個檔案與流相關聯。 流的定向 對於ASCII字符集,一個字元用一個位元組表示,對於國際字符集,一個字元可用多個位元組表示,標準IO可用單位元組或者多位元組(寬)來表示流。流的定向決定了所讀寫的字元是單位元組還是多位元組的。 當一個流最初被建立的時候,它是沒有定向的,如果在未定向的流上用了一個多位元組IO函式那麼該流被設定為寬定向的,如果在未定向的流上使用了一個單位元組IO函式,那麼流被設定為位元組定向的。只有兩個函式可以改變流的定向,fropen函式可以清楚一個流的定向,fwide函式可用於設定流的定向。 int fwide(FILE *stream, int mode); 若流是寬定向的返回正值,若流是位元組定向的返回負值,若未定向返回0
- 若mode引數值為負,fwide試圖指定流為位元組定向
- 若mode引數值為正,fwide試圖指定流為寬定向的
- 若mode引數值為0,fwide不指定流的定向,但返回標識該流定向的值
需要注意的是fwide並不改變一個已經定向的流,還要注意fwide沒有出錯返回 當我們開啟一個流的時候,標準IO函式fopen返回一個指向FILE的指標,該物件結構包括了標準IO庫為管理該流所需要的所有資訊,包括實際IO的檔案描述符,指向用於該緩衝區的指標等 標準輸入/輸出/錯誤 當Linux建立一個新程序的時候,會自動建立三個檔案描述符,分別為0,1,2分別對應標準輸入,標準輸出,標準錯誤。C庫中與檔案描述符相對應的檔案指標。與檔案描述符相似,我們可以用檔案指標操作。 通過分析核心我們可以發現,stdin,stdout,stderr都是FILE型別的檔案指標,是由C庫靜態定義的,通過巨集定義直接與檔案描述符0,1,2相關聯。
緩衝
C庫中的IO對檔案IO進行了封裝,為了提高效能,引入了緩衝機制,共有三種緩衝機制:
- 全快取:一般用於訪問真正的磁碟檔案,C庫會為檔案訪問申請一塊記憶體,只有當檔案內容將快取填滿或執行沖刷函式flush的時候,C庫才會將快取寫入核心。
- 行快取:一般用於終端訪問,當遇到一個換行符的時候,就會引發真正的IO操作,需要注意的是,C庫中的行快取也是固定大小的,因此當快取已滿即使沒有換行符也會引發IO操作
- 不帶緩衝:標準IO庫不對字元進行緩衝
- 需要注意的是標準錯誤是不帶緩衝的這樣錯誤資訊可以儘快顯示出來
ISOC要求有下列特徵:
- 當且僅當標準輸入與標準輸出並不指向互動裝置的時候他們才是全緩衝
- 標準錯誤是不帶緩衝的
- 若是指向終端裝置的流,則是行緩衝,否則是全緩衝
我們通常用下列函式改變緩衝型別 void setbuf(FILE *stream, char *buf); int setvbuf(FILE *stream, char *buf, int mode, size_t size); 這些函式一定要在流已經被開啟的基礎上使用 可以使用setbuf關閉或者開啟緩衝機制,stream是要操作的流,buf是我們提供的緩衝區,當我們要關閉緩衝機制的時候將buf設定成nullptr當我們將buf設定成緩衝區的時候,要是流與終端裝置相關的話那就是行緩衝,否則就是全緩衝 使用setvbuf可以精確的說明所需的緩衝型別這是由引數mode實現的 _IOFBF 全緩衝 _IOLBF 行緩衝 _IONBF 不帶緩衝 若果指定一個不帶緩衝的流,則忽略buf和size引數,如果指定全緩衝或者行緩衝,則buf與size可選擇一個緩衝區長度,如果該流是帶緩衝的,而buf是nullptr則標準IO庫將自動的為流分配適當長度的緩衝區,這個長度由BUFSIZE指定。
流的開啟與讀寫
流的開啟
下面三個函式用來開啟一個流 FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream); 這三個函式的區別如下 fopen函式開啟路徑名為path的一個指定檔案 freopen函式在一個指定的流上開啟一個指定檔案,若該流已經開啟,則先關閉該流,若該流已經定向就清除該定向,此函式一般用於將一個指定的檔案開啟為一個預定義的流 fdopen函式取一個已有的描述符,並使一個標準IO流與該描述符相結合,通常用於管道建立和網路通訊等 type引數指定對該IO的讀寫方式 使用b作為type的一部分是為了使得標準IO區分文字與二進位制,另外標準IO追加寫方式也不能用於建立一個檔案。 當以讀寫方式開啟一個檔案的時候,具有下列限制
- 如果中間沒有fflush,fseek,fsetpos或rewind,則在輸出後面不能直接跟輸入
- 如果中間沒有fseek,fsetpos,rewind或者一個輸入沒有到達檔案尾端,則在輸入後不能直接跟輸出
檔案描述符與流的轉換 linux提供了檔案描述符,而C庫提供了流,C庫是對於系統呼叫的一層封裝,於是我們提供了兩個相互轉換的API FILE *fdopen(int fd, const char *mode); int fileno(FILE *stream); fdopen用於從檔案描述符fd生成一個檔案流FILE,而fileno則用於從檔案流FILE得到對應的檔案描述符檔案流FILE儲存了檔案描述符的值,當從檔案流轉換成檔案描述符的時候,可以直接通過當前FILE儲存的值得到fd,而從檔案描述符轉換成檔案流的時候,C庫返回都是一個重新申請的檔案流FILE。
讀和寫流
一旦打開了流,則可在三種不同型別的非格式化IO中進行選擇,對其進行讀寫操作
- 每次一個字元的IO,一次讀或寫一個字元,如果流是帶緩衝的,則標準IO函式處理所有緩衝
- 每次一行的IO,如果想要一次讀或寫一行,則使用fgets和fputs每行都是換行符結束
- 直接IO,fread與fwrite函式支援這種型別的IO每次IO操作讀或寫某種數量的物件,物件指定長度
每次一個字元的IO 以下三個函式用於一次讀一個字元 int getc(FILE *stream); char *fgets(char *s, int size, FILE *stream); int getchar(void); 前兩個函式的區別在於getc可以被實現為巨集,而fgetc不能 不管是出錯還是達到檔案末尾,這三個函式都返回相同的值為了區分兩種情況,呼叫ferror, ferror用於告訴使用者C庫的檔案流FILE是否有錯誤發生,當有錯誤發生就返回非零值,反之返回0 從流中讀取資料以後,可以呼叫ungetc將字元再壓回流中 int ungetc(int c, FILE *stream); 押送回流中的字元可以再次從流中讀取,但是讀出的順序與押送的相反,回送的字元不一定不許是上一次讀到的字元,不能回送EOF但是當已經到達檔案末尾的時候依舊可以回送一個字元下次讀將返回該字元再讀就返回EOF 用ungetc壓送回字元的時候,並沒有將他們寫道底層裝置上,只是寫回緩衝區 每次一行的IO char *fgets(char *s, int size, FILE *stream); char *gets(char *s); 這兩個函式都指定了緩衝區的地址,讀入的行送入其中 對於fgets,必須指定緩衝的長度n,此函式一直讀到下一個換行符為止,但是不超過n個字元讀入的字元被送入緩衝區,該緩衝區以null位元組結尾。
二進位制IO
在前面我們看了一次一個字元或者一次一行進行操作的函式,可是如果進行二進位制操作的話,我們更願意一次讀或者寫一個結構,如果用getc我們要迴圈整個結構,如果是gets萬一碰到換行符也就沒辦法了,於是我們提供下面兩個函式進行二進位制IO操作 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 第一個引數指向結構,第二個引數是結構大小,第三個引數是結構個數,第四個引數是流 使用二進位制IO的問題是,他只能用於讀在同一系統上已寫的資料,當我們在一個系統上寫的資料要在另一個系統上讀就會發生問題原因是:
- 在同一個結構中,同一成員的偏移量可能隨程式編譯和系統的不同而不同
- 用來儲存多位元組整數和浮點值的二進位制格式在不同的系統結構間也有可能不同