關於C中I/O緩衝區的解釋

來源:互聯網
上載者:User

標籤:style   http   color   使用   檔案   資料   

使用者程式調用C標準I/O庫函數讀寫檔案或裝置,而這些庫函數要通過系統調用把讀寫請求傳給核心,最終由核心驅動磁碟或裝置完成I/O操作。C標準庫為每個開啟的檔案分配一個I/O緩衝區以加速讀寫操作,通過檔案的 FILE結構體可以找到這個緩衝區,使用者調用讀寫函數大多數時候都在I/O緩衝區中讀寫,只有少數時候需要把讀寫請求傳給核心。以 fgetc/ fputc為例,當使用者程式第一次調用 fgetc讀一個位元組時, fgetc函數可能通過系統調用進入核心讀1K位元組到I/O緩衝區中,然後返回I/O緩衝區中的第一個位元組給使用者,把讀寫位置指向I/O緩衝區中的第二個字元,以後使用者再調 fgetc,就直接從I/O緩衝區中讀取,而不需要進核心了,當使用者把這1K位元組都讀完之後,再次調用 fgetc時, fgetc函 數會再次進入核心讀1K位元組到I/O緩衝區中。在這個情境中使用者程式、C標準庫和核心之間的關係就像CPU、Cache和記憶體之間的關係一樣,C標準庫之 所以會從核心預讀一些資料放在I/O緩衝區中,是希望使用者程式隨後要用到這些資料,C標準庫的I/O緩衝區也在使用者空間,直接從使用者空間讀取資料比進核心 讀資料要快得多。另一方面,使用者程式調用 fputc通常只是寫到I/O緩衝區中,這樣 fputc函數可以很快地返回,如果I/O緩衝區寫滿了, fputc就通過系統調用把I/O緩衝區中的資料傳給核心,核心最終把資料寫回磁碟。有時候使用者程式希望把I/O緩衝區中的資料立刻傳給核心,讓核心寫回裝置,這稱為Flush 操作,對應的庫函數是 fflushfclose函數在關閉檔案之前也會做Flush操作。

fgets/fputs示意了I/O緩衝區的作用,使用fgets/fputs函數時在使用者程式中也需要分配緩衝區(圖中的buf1buf2),注意區分使用者程式的緩衝區和C標準庫的I/O緩衝區。

 

 C標準庫的I/O緩衝區:

C標準庫的I/O緩衝區有三種類型:全緩衝、行緩衝和無緩衝。當使用者程式調用庫函數做寫操作時,不同類型的緩衝區具有不同的特性。

全緩衝

如果緩衝區寫滿了就寫回核心。常規檔案通常是全緩衝的。

行緩衝

如果使用者程式寫的資料中有分行符號就把這一行寫回核心,或者如果緩衝區寫滿了就寫回核心。標準輸入和標準輸出對應終端裝置時通常是行緩衝的。

無緩衝

使用者程式每次調庫函數做寫操作都要通過系統調用寫回核心。標準錯誤輸出通常是無緩衝的,這樣使用者程式產生的錯誤資訊可以儘快輸出到裝置。

下面通過一個簡單的例子證明標準輸出對應終端裝置時是行緩衝的。

#include <stdio.h>int main(){  printf("hello world");  while(1);  return 0;}

運行這個程式,會發現hello world並沒有列印到螢幕上。用Ctrl-C終止它,去掉程式中的while(1);語句再試一次:

$ ./a.outhello world$

hello world被列印到螢幕上,後面直接跟Shell提示符,中間沒有換行。

我們知道main函數被啟動代碼這樣調用:exit(main(argc, argv));main函數return時啟動代碼會調用exitexit函數首先關閉所有尚未關閉的FILE *指標(關閉之前要做Flush操作),然後通過_exit系統調用進入核心退出當前進程。

    在上面的例子中,由於標準輸出是行緩衝的,printf("hello world");列印的字串中沒有分行符號,所以只把字串寫到標準輸出的I/O緩衝區中而沒有寫回核心(寫到終端裝置),如果敲Ctrl-C,進程是異常終止的,並沒有調用exit,也就沒有機會Flush I/O緩衝區,因此字串最終沒有列印到螢幕上。如果把列印語句改成printf("hello world\n");,有分行符號,就會立刻寫到終端裝置,或者如果把while(1);去掉也可以寫到終端裝置,因為程式退出時會調用exitFlush所有I/O緩衝區。在本書的其它例子中,printf列印的字串末尾都有分行符號,以保證字串在printf調用結束時就寫到終端裝置。

我們再做個實驗,在程式中直接調用_exit退出。

#include <stdio.h>#include <unistd.h>int main(){  printf("hello world");  _exit(0);}

結果也不會把字串列印到螢幕上,如果把_exit調用改成exit就可以列印到螢幕上。

除了寫滿緩衝區、寫入分行符號之外,行緩衝還有一種情況會自動做Flush操作。如果:

  • 使用者程式調用庫函數從無緩衝的檔案中讀取

  • 或者從行緩衝的檔案中讀取,並且這次讀操作會引發系統調用從核心讀取資料

那麼在讀取之前會自動Flush所有行緩衝。例如:

#include <stdio.h>#include <unistd.h>int main(){  char buf[20];  printf("Please input a line: ");  fgets(buf, 20, stdin);  return 0;}

雖然調用printf並不會把字串寫到裝置,但緊接著調用fgets讀一個行緩衝的檔案(標準輸入),在讀取之前會自動Flush所有行緩衝,包括標準輸出。

如果使用者程式不想完全依賴於自動的Flush操作,可以調fflush函數手動做Flush操作。

#include <stdio.h>int fflush(FILE *stream);傳回值:成功返回0,出錯返回EOF並設定errno

對前面的例子再稍加改動:

#include <stdio.h>int main(){  printf("hello world");  fflush(stdout);  while(1);}

雖然字串中沒有換行,但使用者程式調用fflush強制寫回核心,因此也能在螢幕上列印出字串。fflush函數用於確保資料寫回了核心,以免進程異常終止時遺失資料。作為一個特例,調用fflush(NULL)可以對所有開啟檔案的I/O緩衝區做Flush操作。

 

 

 

 

總結一下:C語言中的I/O緩衝區是這樣的:

對於printf函數,行緩衝,每次輸出有分行符號或輸出超過緩衝區大小的資料時,會在螢幕上輸出資料。

有兩個例外:1.當從無緩衝檔案(或資料流)中讀資料輸出;

                       2.當printf後跟了scanf,由於scanf是行緩衝流,而且調用scanf會引發系統寫資料到內

               核,所以printf會自動flush。

對於scanf函數,行緩衝,每次輸入時會把資料連同末尾的分行符號送進緩衝區,然後緩衝區會有一個“指標”,始終指向當前讀到的那個字元(即“當前字元指標”)。當把字元從緩衝區中送入記憶體中的具體變數中時,清除緩衝區中的該字元。(敲斷行符號輸入的分行符號在緩衝區中當做空格符‘  ‘處理)。值得注意的是,每次scanf時,先檢測緩衝區的“當前字元指標”之後是否有未讀字元。

 這樣,連續scanf  %c資料時,就有可能把資料尾的分行符號直接賦給接下來本要輸入的字元變數。

舉例:(思考)

一個小程式,其中有以下幾句:

char c1,c2;

scanf("%c",&c1);

printf("%c",c1);

scanf("%c",&c2);

printf("%c",c2);

這樣,run以後,輸入c1,敲斷行符號以後就不能輸入c2,程式直接結束了,為什嗎?

 

後來修改一下,

把“scanf("%c",&c2);

        printf("%c",c2);”改成了:

scanf("%s",&c2);

printf("%c",c2);

卻可以,為什嗎?%s和%c有什麼不同?(%s讀取第一個非Null 字元)

 

 

還有一種方法:
scanf("%c",&ch);改為scanf("%c",&ch);%c前面加一個空格,這是為什嗎?

 

 

 

有還不清楚的請直接在留言中提問,謝謝。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.