標準I/O庫(ISO C的標準I/O庫)

本文講述由ISO C定義的標準I/O庫。這個庫已經擁有非常長的歷史了,它由D.R.在1975年左右編寫,現在已經過去45年了。但是ISO C幾乎沒有對標準I/O庫做出修改。不用我說,大家也知道這個庫存在的問題應該是非常多的。

標準輸入,標準輸出,標準出錯

Linux下的不帶緩衝的I/O是圍繞文件描述符來展開的。標準庫的則不是,標準庫的操作是圍繞流(stream)這個概念來進行的。例如:標準輸入流,標準輸出流,標準出錯流。這3個流是自動被進程使用的。他們其實和文件描述符STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO引用相同的文件。

帶緩衝的I/O操作

使用文件描述符的I/O是不帶緩衝的(當然了,這裏所說的不帶緩衝指的是進程中使用這兩個函數不會自動緩衝,每使用一次就會進行一次系統調用,實際上除了原始磁盤I/O之外,其它的所有磁盤I/O都會經過內核緩衝區。),而標準I/O庫爲了減少read和write操作,使用了緩衝。

標準I/O提供了緩衝,但是成也蕭何,敗也蕭何啊!這個緩衝的設計也是它的敗筆吧!

標準I/O提供了3種緩衝方式。

全緩衝

在這種情況下,在填滿標準I/O緩衝區以後,才進行I/O操作。在第一次執行I/O操作的時候,標準I/O會使用malloc來獲取所需要的緩衝區。那麼這時候有個問題,緩衝區沒滿就不進行實際的I/O操作,但是你想寫入磁盤,怎麼辦?別慌,標準I/O設計了一個沖洗(flush)操作。你可以使用函數fflush來強制沖洗一個流。沖洗意味着將緩衝區的內容寫到磁盤中。

函數fflush()

它有一個特殊情形,就是參數stream是NULL。這個時候表示強制沖洗所有輸出流。

行緩衝

行緩衝就是當輸入和輸出中遇到換行符時,標準I/O執行實際I/O操作。當我們使用scanf和printf的時候,實際上就是行緩衝在起作用。行緩衝的長度是固定的,因此如果你在一行輸入的內容過的,導致在你還沒有換行的時候,也會發生實際的I/O操作。還有就是當你通過標準I/O庫從一個不帶緩衝或者是帶行緩衝的流得到輸入數據。那麼就會強制沖洗所有行緩衝的輸出流。

不緩衝

標準I/O對字符不進行緩衝。通常標準出錯是不帶緩衝的,這樣就能使的出錯信息及時打印出來。

ISO C的規則

  • 當且僅當標準輸入和標準輸出不指向交互式設備的時候,它們纔是全緩衝的。
  • 標準錯誤一定不會是全緩衝。

規則就是如此的簡單粗暴。它只說了什麼時候全緩衝和不全緩衝。在Linux下。通常是這樣的。

  • 標準錯誤是不帶緩衝的。
  • 標準輸入和標準輸出,如果指向的設備是終端,那麼使用行緩衝,否則使用全緩衝。

更改緩衝方式

我們可以使用下面的庫函數來更改緩衝方式。

這些函數的只能在打開流之後調用。所以我們可以看到這些函數的第一個參數都是FILE *。需要注意的是setbuf(),setbuffer()以及setlinebuf其實都將調用setvbuf函數。因此,我們來關注一下setvbuf()函數。

 也就是說buf和size是由mode決定的。但是當buf是NULL時,標準I/O會自動爲該流分配適當長度的緩衝區(就是size所指定的值)。當然只有這個被指定的模式會受到影響,下次還是會新分配緩衝的。

其餘的函數說明如下:

打開流操作

在Linux下這三個函數可以用來打開流。仔細觀察可以發現fdopen()函數需要一個文件描述符做參數。而ISO C沒有涉及文件描述符,所以只能在POSIX標準之下使用這個函數。另外對於fdopen()而言,它的mode參數的含義也略有不同。這是因爲文件的權限在被open或者creat的時候已經指定好了。並且fdopen()函數並不能用來創建一個文件,很明顯它需要一個文件描述符,既然有了文件描述符,那麼文件肯定已經存在了。好了,下面我們先看一下mode的取值。

值得注意的是Linux內核並不區分文本文件和二進制文件。因此在Linux下使用帶有b的參數是沒有意義的(沒有作用)。

讀和寫流

輸入函數

標準I/O庫提供了非常多的函數來進行讀寫操作。下面給出一些讀寫相關的函數。

有個問題需要注意,那就是返回值。

fgetc(),getc()和getchar()無論是遇到文件結尾還是錯誤都會返回同樣的值。爲了區分這兩種情形,必須使用ferror或者feof函數。

clearerr()函數可以用來清楚1.出錯標誌,2.文件結束標誌。上述的fileno函數可以被實現爲宏。宏和函數的區別還是比較大的。在使用某些函數的時候,需要注意它是否被實現爲宏,如果是,那麼意味着一下幾點:

1.參數不要具備副作用。

2.不能傳遞宏的地址,它沒有地址。

3.宏比函數快。

輸出函數

上述的函數之中,gets()函數由於沒有指定緩衝區的大小。這曾造成過1988年的蠕蟲事件。因此,當大多數人在Visual Studio2015之後的版本上書寫C語言程序的時候,使用gets和scanf函數會報錯。

VS不僅報錯了,還讓你使用scanf_s()函數來代替scanf函數。但是帶來問題是比較差的可移植性。在某些類Unix操作系統上已經棄用了該接口。我們儘量不要使用不安全的函數。

二進制I/O

前面的I/O函數都是一次讀寫一行或者是單個字符,這在讀寫大文件的時候並不適合。爲此,提供了下面的函數來執行二進制I/O操作。

這兩個函數仍舊存在一些問題。那就是在不同的系統上工作的時候,可能由於struct對齊方式,以及是否遵從IEEE 754標準造成程序出錯。多年之前,所有的Unix操作系統都運行在PDP-11計算機上,所以沒有任何問題。

定位流

上述函數在類Unix系統上沒有問題,但是如果在Window下可能就行不通。ISO C提供了fgetpos()和fsetpos()函數。

格式化I/O

格式化I/O能夠漂亮的處理輸入輸出,但是格式轉換符比較複雜,種類繁多。在此處不說明。只給出相關的函數。

在Unix中,標準I/O庫最後還是需要調用不帶緩衝的I/O函數。每個標準I/O都有一個與其相關聯的文件描述符,可以使用fileno()函數來獲得文件描述符。需要注意的是fileno()函數是POSIX標準提供的。

標準I/O的問題以及替代方式

前面已經說過了,標準I/O的歷史已經非常長了,它存在問題也比較多。很明顯標準I/O的效率不高。它需要在內核緩衝區複製一次數據,然後在用戶進程內存中在複製一次數據。

另外的問題可能就是不夠安全,微軟已經在Windows平臺提供了更加安全的函數。

在Linux下替代它們的可以有sfio庫,以及使用mmap()函數的ASI包。

前文說過成也蕭何,敗也蕭何。標準I/O使用的緩衝技術正是產生很多問題和混淆的地方。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章