緩存io和非緩沖io


首先,先稍微了解系統調用的概念:

    系統調用,英文名system call,每個操作系統都在內核里有一些內建的函數庫,這些函數可以用來完成一些系統系統調用把應用程序的請求傳給內核,調用相應的的內核函數完成所需的處理,將處理結果返回給應用程序,如果沒有系統調用和內核函數,用戶將不能編寫大型應用程序,及別的功能,這些函數集合起來就叫做程序接口或應用編程接口(Application Programming Interface,API),我們要在這個系統上編寫各種應用程序,就是通過這個API接口來調用系統內核里面的函數。如果沒有系統調用,那么應用程序就失去內核的支持。

    現在,再聊不帶緩存的I/O操作:

    linix對IO文件的操作分為不帶緩存的IO操作和標准IO操作(即帶緩存),剛開始,要明確以下幾點:

    1:不帶緩存,不是直接對磁盤文件進行讀取操作,像read()和write()函數,它們都屬於系統調用,只不過在用戶層沒有緩存,所以叫做無緩存IO,但對於內核來說,還是進行了緩存,只是用戶層看不到罷了。如果這一點看不懂,請看第二點;

    2:帶不帶緩存是相對來說的,如果你要寫入數據到文件上時(就是寫入磁盤上),內核先將數據寫入到內核中所設的緩沖儲存器,假如這個緩沖儲存器的長度是100個字節,你調用系統函: 
    ssize_t write (int fd,const void * buf,size_t count);
    寫操作時,設每次寫入長度count=10個字節,那么你幾要調用10次這個函數才能把這個緩沖區寫滿,此時數據還是在緩沖區,並沒有寫入到磁盤,緩沖區滿時才進行實際上的IO操作,把數據寫入到磁盤上,所以上面說的“不帶緩存""不是沒有緩存而是沒有直寫進磁盤就是這個意思

    那么,既然不帶緩存的操作實際在內核是有緩存器的,那帶緩存的IO操作又是怎么回事呢?

    帶緩存IO也叫標准IO,符合ANSI C 的標准IO處理,不依賴系統內核,所以移植性強,我們使用標准IO操作很多時候是為了減少對read()和write()的系統調用次數,帶緩存IO其實就是在用戶層再建立一個緩存區,這個緩存區的分配和優化長度等細節都是標准IO庫代你處理好了,不用去操心,還是用上面那個例子說明這個操作過程:

    上面說要寫數據到文件上,內核緩存(注意這個不是用戶層緩存區)區長度是100字節,我們調用不帶緩存的IO函數write()就要調用10次,這樣系統效率低,現在我們在用戶層建立另一個緩存區(用戶層緩存區或者叫流緩存),假設流緩存的長度是50字節,我們用標准C庫函數的fwrite()將數據寫入到這個流緩存區里面,流緩存區滿50字節后在進入內核緩存區,此時再調用系統函數write()將數據寫入到文件(實質是磁盤)上,看到這里,你用該明白一點,標准IO操作fwrite()最后還是要掉用無緩存IO操作write,這里進行了兩次調用fwrite()寫100字節也就是進行兩次系統調用write()。

    如果看到這里還沒有一點眉目的話,那就比較麻煩了,希望下面兩條總結能夠幫上忙:
    無緩存IO操作數據流向路徑:數據——內核緩存區——磁盤
    標准IO操作數據流向路徑:數據——流緩存區——內核緩存區——磁盤

    下面是一個網友的見解,以供參考:

    不帶緩存的I/O對文件描述符操作,下面帶緩存的I/O是針對流的。

    標准I/O庫就是帶緩存的I/O,它由ANSI C標准說明。當然,標准I/O最終都會調用上面的I/O例程。標准I/O庫代替用戶處理很多細節,比如緩存分配、以優化長度執行I/O等。

    標准I/O提供緩存的目的就是減少調用read和write的次數,它對每個I/O流自動進行緩存管理(標准I/O函數通常調用malloc來分配緩存)。它提供了三種類型的緩存:

    1) 全緩存。當填滿標准I/O緩存后才執行I/O操作。磁盤上的文件通常是全緩存的。

    2) 行緩存。當輸入輸出遇到新行符或緩存滿時,才由標准I/O庫執行實際I/O操作。stdin、stdout通常是行緩存的。

    3) 無緩存。相當於read、write了。stderr通常是無緩存的,因為它必須盡快輸出。

    一般而言,由系統選擇緩存的長度,並自動分配。標准I/O庫在關閉流的時候自動釋放緩存。

    在標准I / O庫中,一個效率不高的不足之處是需要復制的數據量。當使用每次一行函數fgets和fputs時,通常需要復制兩次數據:一次是在內核和標准I / O緩存之間(當調用read和write時),第二次是在標准I / O緩存(通常系統分配和管理)和用戶程序中的行緩存(fgets的參數就需要一個用戶行緩存指針)之間。

    不管上面講的到底懂沒懂,記住一點:

    使用標准I / O例程的一個優點是無需考慮緩存及最佳I / O長度的選擇,並且它並不比直接調用read、write慢多少。

    帶緩存的文件操作是標准C 庫的實現,第一次調用帶緩存的文件操作函數時標准庫會自動分配內存並且讀出一段固定大小的內容存儲在緩存中。所以以后每次的讀寫操作並不是針對硬盤上的文件直接進行的,而是針對內存中的緩存的。何時從硬盤中讀取文件或者向硬盤中寫入文件有標准庫的機制控制。不帶緩存的文件操作通常都是系統提供的系統調用,更加低級,直接從硬盤中讀取和寫入文件,由於 IO瓶頸的原因,速度並不如意,而且原子操作需要程序員自己保證,但使用得當的話效率並不差。另外標准庫中的帶緩存文件IO 是調用系統提供的不帶緩存IO實現的。

    這里為了說明標准I/O的工作原理,借用了glibc中標准I/O實現的細節,所以代碼多是不可移植的.

    1. buffered I/O, 即標准I/O

    首先,要明確,unbuffered I/O只是相對於buffered I/O,即標准I/O來說的。而不是說unbuffered I/O讀寫磁盤時不用緩沖。實際上,內核是存在高速緩沖區來進行。真正的磁盤讀寫的,不過這里要討論的buffer跟內核中的緩沖區無關.
    buffered I/O的目的是什么呢?很簡單,buffered I/O的目的就是為了提高效率.請明確一個關系,那就是:
    buffered I/O庫函數(fread, fwrite等,用戶空間) <----call--->  unbuffered I/O系統調用(read,write等,內核空間)<-------> 讀寫磁盤
    buffered I/O庫函數都是調用相關的unbuffered I/O系統調用來實現的,他們並不直接讀寫磁盤.那么,效率的提高從何而來呢?
    注意到,buffered I/O中都是庫函數,而unbuffered I/O中為系統調用,使用庫函數的效率是高於使用系統調用的.buffered I/O就是通過盡可能的少使用系統調用來提高效率的.它的基本方法是,在用戶進程空間維護一塊緩沖區,第一次讀(庫函數)的時候用read(系統調用)多從內核讀出一些數據,下次在要讀(庫函數)數據的時候,先從該緩沖區讀,而不用進行再次read(系統調用)了.同樣,寫的時候,先將數據寫入(庫函數)一個緩沖區,多次以后,在集中進行一次write(系統調用),寫入內核空間.
    buffered I/O中的fgets, puts, fread, fwrite等和unbufferedI/O中的read,write等就是調用和被調用的關系

    下面是一個利用buffered I/O讀取數據的例子:

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   char buf[5];  
  10.   FILE *myfile = stdin;  
  11.   fgets(buf, 5, myfile);  
  12.   fputs(buf, myfile);  
  13.     
  14.   return 0;  
  15. }  

    buffered I/O中的"buffer"到底是指什么呢?這個buffer在什么地方呢?FILE是什么呢?它的空間是怎么分配的呢?
    要弄清楚這些問題,就要看看FILE是如何定義和運作的了.(特別說明,在平時寫程序時,不用也不要關心FILE是如何定義和運作的,最好不要直接操作它,這里使用它,只是為了說明buffered IO)
    下面的這個是glibc給出的FILE的定義,它是實現相關的,別的平台定義方式不同.
   

[cpp]  view plain copy
 
  1. struct _IO_FILE {  
  2.   int _flags;   
  3.   #define _IO_file_flags _flags  
  4.   char* _IO_read_ptr;  
  5.   char* _IO_read_end;  
  6.   char* _IO_read_base;  
  7.   char* _IO_write_base;  
  8.   char* _IO_write_ptr;  
  9.   char* _IO_write_end;  
  10.   char* _IO_buf_base;  
  11.   char* _IO_buf_end;  
  12.   char *_IO_save_base;  
  13.   char *_IO_backup_base;  
  14.   char *_IO_save_end;  
  15.   struct _IO_marker *_markers;  
  16.   struct _IO_FILE *_chain;  
  17.   int _fileno;  
  18. };  

    上面的定義中有三組重要的字段:
    1.char* _IO_read_ptr;        char* _IO_read_end;    char* _IO_read_base;
    2.char* _IO_write_base;   char* _IO_write_ptr;      char* _IO_write_end;
    3.char* _IO_buf_base;      char* _IO_buf_end;
    其中
    _IO_read_base 指向"讀緩沖區"
    _IO_read_end  指向"讀緩沖區"的末尾
    _IO_read_end - _IO_read_base "讀緩沖區"的長度
    _IO_write_base 指向"寫緩沖區"
    _IO_write_end 指向"寫緩沖區"的末尾
    _IO_write_end - _IO_write_base "寫緩沖區"的長度
    _IO_buf_base  指向"緩沖區"
    _IO_buf_end   指向"緩沖區"的末尾
    _IO_buf_end - _IO_buf_base "緩沖區"的長度
    上面的定義貌似給出了3個緩沖區,實際上上面的_IO_read_base,_IO_write_base, _IO_buf_base都指向了同一個緩沖區.這個緩沖區跟上面程序中的char buf[5];沒有任何關系.
他們在第一次buffered I/O操作時由庫函數自動申請空間,最后由相應庫函數負責釋放.(再次聲明,這里只是glibc的實現,別的實現可能會不同,后面就不再強調了)
     請看下面的程序(這里給的是stdin,行緩沖的例子):
   

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   char buf[5];  
  10.   FILE *myfile =stdin;  
  11.   printf("before reading\n");  
  12.   printf("read buffer base %p\n", myfile->_IO_read_base);  
  13.   printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);  
  14.   printf("write buffer base %p\n", myfile->_IO_write_base);  
  15.   printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);  
  16.   printf("buf buffer base %p\n", myfile->_IO_buf_base);  
  17.   printf("buf buffer length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);  
  18.   printf("\n");  
  19.   fgets(buf, 5, myfile);  
  20.   fputs(buf, myfile);  
  21.   printf("\n");  
  22.   printf("after reading\n");  
  23.   printf("read buffer base %p\n", myfile->_IO_read_base);  
  24.   printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);  
  25.   printf("write buffer base %p\n", myfile->_IO_write_base);  
  26.   printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);  
  27.   printf("buf buffer base %p\n", myfile->_IO_buf_base);  
  28.   printf("buf buffer length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);  
  29.   
  30.   return 0;  
  31. }  

    可以看到,在讀操作之前,myfile的緩沖區是沒有被分配的,在一次讀之后,myfile的緩沖區才被分配.這個緩沖區既不是內核中的緩沖區,也不是用戶分配的緩沖區,而是有用戶進程空間中的由buffered I/O系統負責維護的緩沖區.(當然,用戶可以可以維護該緩沖區,這里不做討論了)

 

    上面的例子只是說明了buffered I/O緩沖區的存在,下面從全緩沖,行緩沖和無緩沖3個方面看一下buffered I/O是如何工作的.

    1.1. 全緩沖

    下面是APUE上的原話:
    全緩沖"在填滿標准I/O緩沖區后才進行實際的I/O操作.對於駐留在磁盤上的文件通常是由標准I/O庫實施全緩沖的"書中這里"實際的I/O操作"實際上容易引起誤導,這里並不是讀寫磁盤,而應該是進行read或write的系統調用
    下面兩個例子會說明這個問題

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   char buf[5];  
  10.   char *cur;  
  11.   FILE *myfile;  
  12.   myfile = fopen("bbb.txt", "r");  
  13.   printf("before reading, myfile->_IO_read_ptr: %d\n", myfile->_IO_read_ptr - myfile->_IO_read_base);  
  14.   fgets(buf, 5, myfile); //僅僅讀4個字符  
  15.   cur = myfile->_IO_read_base;  
  16.   while (cur < myfile->_IO_read_end) //實際上讀滿了這個緩沖區  
  17.   {  
  18.     printf("%c",*cur);  
  19.     cur++;  
  20.   }  
  21.   printf("\nafter reading, myfile->_IO_read_ptr: %d\n", myfile->_IO_read_ptr - myfile->_IO_read_base);  
  22.   return 0;  
  23. }  

    上面提到的bbb.txt文件的內容是由很多行的"123456789"組成
    上例中,fgets(buf, 5, myfile); 僅僅讀4個字符,但是,緩沖區已被寫滿,但是_IO_read_ptr卻向前移動了5位,下次再次調用讀操作時,只要要讀的位數不超過myfile->_IO_read_end - myfile->_IO_read_ptr那么就不需要再次調用系統調用read,只要將數據從myfile的緩沖區拷貝到buf即可(從myfile->_IO_read_ptr開始拷貝)

 

     
    全緩沖讀的時候:

    _IO_read_base始終指向緩沖區的開始
    _IO_read_end始終指向已從內核讀入緩沖區的字符的下一個(對全緩沖來說,buffered I/O讀每次都試圖都將緩沖區讀滿)
    _IO_read_ptr始終指向緩沖區中已被用戶讀走的字符的下一個
   (_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時則已經到達文件末尾,其中_IO_buf_base-_IO_buf_end是緩沖區的長度
    一般大體的工作情景為:
    第一次fgets(或其他的)時,標准I/O會調用read將緩沖區充滿,下一次fgets不調用read而是直接從該緩沖區中拷貝數據,直到緩沖區的中剩余的數據不夠時,再次調用read.在這個過程中,_IO_read_ptr就是用來記錄緩沖區中哪些數據是已讀的,哪些數據是未讀的

 

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   char buf[2048]={0};  
  10.   int i;  
  11.   FILE *myfile;  
  12.   myfile = fopen("aaa.txt", "r+");  
  13.   i= 0;  
  14.   while (i<2048)  
  15.   {  
  16.     fwrite(buf+i, 1, 512, myfile);  
  17.     i +=512;  
  18.     //注釋掉這句則可以寫入aaa.txt  
  19.     myfile->_IO_write_ptr = myfile->_IO_write_base;  
  20.     printf("%p write buffer base\n", myfile->_IO_write_base);  
  21.     printf("%p buf buffer base \n", myfile->_IO_buf_base);  
  22.     printf("%p read buffer base \n", myfile->_IO_read_base);  
  23.     printf("%p write buffer ptr \n", myfile->_IO_write_ptr);  
  24.     printf("\n");  
  25.   }  
  26.   return 0;  
  27. }  

    上面這個是關於全緩沖寫的例子.
    全緩沖時,只有當標准I/O自動flush(比如當緩沖區已滿時)或者手工調用fflush時,標准I/O才會調用一次write系統調用.例子中,fwrite(buf+i, 1, 512, myfile);這一句只是將buf+i接下來的512個字節寫入緩沖區,由於緩沖區未滿,標准I/O並未調用write.此時,myfile->_IO_write_ptr = myfile->_IO_write_base;會導致標准I/O認為沒有數據寫入緩沖區,所以永遠不會調用write,這樣aaa.txt文件得不到寫入.注釋掉myfile->_IO_write_ptr = myfile->_IO_write_base;前后,看看效果.

 

    全緩沖寫的時候:
    _IO_write_base始終指向緩沖區的開始
    _IO_write_end全緩沖的時候,始終指向緩沖區的最后一個字符的下一個
    (對全緩沖來說,buffered I/O寫總是試圖在緩沖區寫滿之后,再系統調用write)
    _IO_write_ptr始終指向緩沖區中已被用戶寫入的字符的下一個

    flush的時候,將_IO_write_base和_IO_write_ptr之間的字符通過系統調用write寫入內核

    1.2. 行緩沖

    下面是APUE上的原話:
    行緩沖"當輸入輸出中遇到換行符時,標准I/O庫執行I/O操作. "書中這里"執行O操作"也容易引起誤導,這里不是讀寫磁盤,而應該是進行read或write的系統調用

    下面兩個例子會說明這個問題
    第一個例子可以用來說明下面這篇帖子的問題
    http://bbs.chinaunix.net/viewthread.php?tid=954547

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3.   
  4. int main(void)  
  5. {  
  6.   char buf[5];  
  7.   char buf2[10];  
  8.     
  9.   fgets(buf, 5, stdin); //第一次輸入時,超過5個字符  
  10.   
  11.   puts(stdin->_IO_read_ptr);//本句說明整行會被一次全部讀入緩沖區,而非僅僅上面需要的個字符  
  12.   stdin->_IO_read_ptr = stdin->_IO_read_end; //標准I/O會認為緩沖區已空,再次調用read,注釋掉,再看看效果  
  13.   printf("\n");  
  14.   puts(buf);  
  15.     
  16.   fgets(buf2, 10, stdin);  
  17.   puts(buf2);  
  18.     
  19.   return 0;  
  20. }  

    上例中, fgets(buf, 5, stdin); 僅僅需要4個字符,但是,輸入行中的其他數據也被寫入緩沖區,但是_IO_read_ptr向前移動了5位,下次再次調用fgets操作時,就不需要再次調用系統調用read,只要將數據從stdin的緩沖區拷貝到buf2即可(從stdin->_IO_read_ptr開始拷貝),stdin->_IO_read_ptr = stdin->_IO_read_end;會導致標准I/O會認為緩沖區已空,再次fgets則需要再次調用read.比較一下將該句注釋掉前后的效果

    行緩沖讀的時候,
    _IO_read_base始終指向緩沖區的開始
    _IO_read_end始終指向已從內核讀入緩沖區的字符的下一個
    _IO_read_ptr始終指向緩沖區中已被用戶讀走的字符的下一個
    (_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時則已經到達文件末尾
    其中_IO_buf_base-_IO_buf_end是緩沖區的長度
   

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. char buf[5]={'1','2', '3', '4', '5'}; //最后一個不要是\n,是\n的話,標准I/O會自動flush的,這是行緩沖跟全緩沖的重要區別  
  8.   
  9. void writeLog(FILE *ftmp)  
  10. {  
  11.   fprintf(ftmp, "%p write buffer base\n", stdout->_IO_write_base);  
  12.   fprintf(ftmp, "%p buf buffer base \n", stdout->_IO_buf_base);  
  13.   fprintf(ftmp, "%p read buffer base \n", stdout->_IO_read_base);  
  14.   fprintf(ftmp, "%p write buffer ptr \n", stdout->_IO_write_ptr);  
  15.   fprintf(ftmp, "\n");  
  16. }  
  17.   
  18. int main(void)  
  19. {  
  20.   int i;  
  21.   FILE *ftmp;  
  22.   ftmp = fopen("ccc.txt", "w");  
  23.   i= 0;  
  24.   while (i<4)  
  25.   {  
  26.     fwrite(buf, 1, 5, stdout);  
  27.     i++;  
  28.     *stdout->_IO_write_ptr++ = '\n';//可以單獨把這句打開,看看效果  
  29.     //getchar();//getchar()會將標准I/O將緩沖區輸出  
  30.     //打開下面的注釋,你就會發現屏幕上什么輸出也沒有  
  31.     //stdout->_IO_write_ptr = stdout->_IO_write_base;  
  32.     writeLog(ftmp); //這個只是為了查看緩沖區指針的變化    
  33.   }  
  34.   return 0;  
  35. }  

    這個例子將將FILE結構中指針的變化寫入的文件ccc.txt,運行后可以有興趣的話,可以看看.
   上面這個是關於行緩沖寫的例子.
    stdout->_IO_write_ptr = stdout->_IO_write_base;會使得標准I/O認為緩沖區是空的,從而沒有任何輸出.可以將上面程序中的注釋分別去掉,看看運行結果

 

    行緩沖時,下面3個條件之一會導致緩沖區立即被flush
    1. 緩沖區已滿
    2. 遇到一個換行符;比如將上面例子中buf[4]改為'\n'時
    3. 再次要求從內核中得到數據時;比如上面的程序加上getchar()會導致馬上輸出

    行緩沖寫的時候:
    _IO_write_base始終指向緩沖區的開始
    _IO_write_end始終指向緩沖區的開始
    _IO_write_ptr始終指向緩沖區中已被用戶寫入的字符的下一個

    flush的時候,將_IO_write_base和_IO_write_ptr之間的字符通過系統調用write寫入內核

    1.3. 無緩沖

    無緩沖時,標准I/O不對字符進行緩沖存儲.典型代表是stderr,這里的無緩沖,並不是指緩沖區大小為0,其實,還是有緩沖的,大小為1

 

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   fputs("stderr", stderr);  
  10.   printf("%d\n", stderr->_IO_buf_end - stderr->_IO_buf_base);  
  11.   
  12.   return 0;  
  13. }  

    對無緩沖的流的每次讀寫操作都會引起系統調用

 

    1.4 feof的問題

    CU上已經有無數的帖子在探討feof了,這里從緩沖區的角度去考察一下.對於一個空文件,為什么要先讀一下,才能用feof判斷出該文件到了結尾了呢?

[cpp]  view plain copy
 
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(void)  
  8. {  
  9.   char buf[5];  
  10.   char buf2[10];  
  11.   
  12.   fgets(buf, sizeof(buf), stdin);//輸入要於4個,少於13個字符才能看出效果  
  13.   puts(buf);  
  14.   
  15.   //交替注釋下面兩行  
  16.   //stdin->_IO_read_end = stdin->_IO_read_ptr+1;  
  17.   
  18.   stdin->_IO_read_end = stdin->_IO_read_ptr + sizeof(buf2)-1;  
  19.      
  20.   fgets(buf2, sizeof(buf2), stdin);  
  21.   puts(buf2);  
  22.   if (feof(stdin))  
  23.     printf("input end\n");  
  24.   return 0;  
  25. }  

    運行上面的程序,輸入多於4個,少於13個字符,並且以連按兩次ctrl+d為結束(不要按回車)從上面的例子,可以看出,每當滿足(_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時,標准I/O則認為已經到達文件末尾,feof(stdin)才會被設置,其中_IO_buf_base-_IO_buf_end是緩沖區的長度.也就是說,標准I/O是通過它的緩沖區來判斷流是否要結束了的.這就解釋了為什么即使是一個空文件,標准I/O也需要讀一次,才能使用feof判斷釋放為空

 

    1.5. 其他說明

    很多新手有一個誤解,就是fgets, fputs代表行緩沖,fread, fwrite代表全緩沖 fgetc, fputc代表無緩沖等等.其實不是這樣的,是什么樣的緩沖跟使用那個函數沒有關系,而跟你讀寫什么類型的文件有關系.上面的例子中多次在全緩沖中使用fgets, fputs,而在行緩沖中使用fread, fwrite
    下面的是引至APUE的,實際上ISO C要求:
    1.當且僅當標准輸入和標准輸出並不涉及交互式設備時,他們才是全緩沖的
    2.標准錯誤輸出決不是全緩沖的.

    很多系統默認使用下列類型的標准:
    1.標准錯誤輸出是不帶緩沖的.
    2.如若是涉及終端設備的其他流,則他們是行緩沖的;否則是全緩沖的.

 

 

---------------------------------------------------------------------------------------------

stdout, stdin, stderr的中文名字分別是標准輸出,標准輸入和標准錯誤。

在Linux下,當一個用戶進程被創建的時候,系統會自動為該進程創建三個數據流,也就是題目中所提到的這三個。那么什么是數據流呢(stream)?我們知道,一個程序要運行,需要有輸入、輸出,如果出錯,還要能表現出自身的錯誤。這是就要從某個地方讀入數據、將數據輸出到某個地方,這就夠成了數據流。

因此,一個進程初期所擁有的這么三個數據流,就分別是標准輸出、標准輸入和標准錯誤,分別用stdout, stdin, stderr來表示。對於這三個數據流來說,默認是表現在用戶終端上的,比如我們在c中使用fprintf:

fprintf(stdout,"hello world!\n");

屏幕上將打印出"hello world!"來,同樣,我們使用:
上面的代碼會接收用戶輸入在終端里的字符,並存在ptr中。

fread(ptr,1,10, stdin);


那么標准輸入輸出和錯誤是不是只能反應在終端里呢?答案是不是的!我們可以將標准輸入和輸出重定位到文件中:

例如,我們使用ls命令,會把當前目錄下的文件名輸出到終端上:

$ls
gcc gcc.sh gmp-5.0.1 gmp-5.0.1.tar.bz2 linux-loongson-community-2.6.35-rc1 longene-0.3.0-linux-2.6.34 mpfr-3.0.0 mpfr-3.0.0.tar.gz oprofile-0.9.6

我們可以使用 “ > ”符號,將ls的標准輸出重定向到文件中:

$ls> lsout //將標准輸出重定向為文件lsout
$more lsout //顯示lsout文件里的內容
gcc gcc.sh gmp-5.0.1 gmp-5.0.1.tar.bz2 linux-loongson-community-2.6.35-rc1 longene-0.3.0-linux-2.6.34 mpfr-3.0.0 mpfr-3.0.0.tar.gz oprofile-0.9.6

同樣,我們也可以使用“ < ”符號將標准輸入重定向到文件中,以sort為例,以下示例使用 sort 命令對由鍵盤鍵入的文本進行排序。鍵入ctrl-D 結束標准輸入。終端屏幕顯示的標准輸出如下:

$sort
muffy
happy
bumpy
CTRL-D // 結束標准輸入。
 
bumpy
happy
muffy //結束標准輸出。

使用" < "重定向后為:

$ more socks 顯示 socks 的內容。
polka dot
argyle
plaid 
 
$ sort< socks 將輸入重定向為從 socks 輸入,並將內容排序。
argyle
plaid
polka dot

好,基本知識講完了,我們知道,標准輸出和標准錯誤默認都是將信息輸出到終端上,那么他們有什么區別呢?讓我們來看個題目:

問題:下面程序的輸出是什么?(intel筆試2011)

int main(){
fprintf(stdout,"Hello ");
fprintf(stderr,"World!");
return0;
}

解答:這段代碼的輸出是什么呢?你可以快速的將代碼敲入你電腦上(當然,拷貝更快),然后發現輸出是

World!Hello

這是為什么呢?在默認情況下,stdout是行緩沖的,他的輸出會放在一個buffer里面,只有到換行的時候,才會輸出到屏幕。而stderr是無緩沖的,會直接輸出,舉例來說就是printf(stdout, "xxxx") 和 printf(stdout, "xxxx\n"),前者會憋住,直到遇到新行才會一起輸出。而printf(stderr, "xxxxx"),不管有么有\n,都輸出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM