麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 系統 > Unix > 正文

UNIX高級環境編程(6)標準IO函數庫

2024-06-28 13:21:46
字體:
來源:轉載
供稿:網友
UNIX高級環境編程(6)標準IO函數庫 - 流的概念和操作

標準IO函數庫隱藏了buffer大小和分配的細節,使得我們可以不用關心預分配的內存大小是否正確的問題。

雖然這使得這個函數庫很容易用,但是如果我們對函數的原理不熟悉的話,也容易遇到很多問題。

?

1 流和FILE實體(Streams and FILE Objects)

前面的章節中,IO集中在文件描述符,每一個打開的文件都對應一個文件描述符,通過文件描述符對文件進行操作。

現在使用了標準IO庫,討論的重點集中在流(streams)。

簡要了解一下流:

  • 當我們打開或創建了一個文件,我們說我們有一個流和該文件關聯。
  • stream支持單字節字符集和多字節字符集。stream的屬性orientation決定使用單字符集還是多字符集。
  • 當一個stream被創建時,沒有指定orientation,這時,當使用寬字符集IO函數時,流的orientation設置為支持寬字符集;當使用單字符集IO函數時,流的orientation設置為支持單字符集。

只有兩個函數可以修改流的orientation:

  • freopen會清除流的orientation;
  • fwide用來設置流的orientation。

fwide函數聲明:

#include <stdio.h>

#include <wchar.h>

int fwide(FILE* fp, int mode);

函數返回值:

  • 返回整數表示支持多字節字符集;
  • 返回負數表示支持單字節字符集;
  • 返回0表示沒有設置stream的orientation。

mode取值的不同決定函數fwide的不同的行為:

  • 如果mode為負數,fwide試著設置指定流支持單字節字符集;
  • 如果mode為整數,fwide試著設置指定流支持多字節字符集;
  • 如果mode為0,fwide不會試著設置流的orientation,但是會返回一個值代表當前流的orientation。

當我們打開一個流,函數fopen返回一個指向FILE對象的指針。FILE對象通常是一個結構體,包含所有控制流所需要的信息,包括:

  • 實際IO所用的文件描述符;
  • 一個指向流所使用的buffer的指針;
  • buffer的大?。?/li>
  • 當前在buffer中的字符數;
  • error flag;
  • 等。

?

2 緩存(Buffering)

緩存(buffering)的作用是為了盡可能少地調用read和write系統調用。

標準IO庫提供三種類型的buffering:

完全緩存(Fully buffered):在這種緩存機制中,實際的IO操作發生在緩存被寫滿時。正在寫入硬盤的文件被完全緩存在buffer中。緩存空間往往在第一次IO操作時通過調用malloc函數獲取;

行緩存(Line buffered):在這種緩存機制中,實際的IO操作發生在新的一行字符被讀入或者輸出時,所以允許每一次只輸出一個字符。行緩存有兩點需要注意:buffer的大小是固定的,所以即使當前行沒有讀入或輸出結束,依然可能發生實際的IO,當buffer被寫滿時;一旦有輸入(從無緩存流或者行緩存流中輸入)發生,所以已在buffer中緩存的輸出流都會被立刻輸出(flush)。

flush:標準IO緩存中內容立刻寫入硬盤或者輸出。在終端設備中,flush的作用也可能是丟棄緩存中得數據。

無緩存(Unbuffered):不緩存輸入或輸出內容。例如,如果我們使用fputs函數輸出15個字符,那么我們希望這15個字符盡可能快地被打印出來。如標準錯誤輸出就要求是無緩存輸出。

ISO C標準要求下面的緩存特性:

  1. 標準輸入輸出在不關聯交互設備的請款下,使用完全緩存(fully buffered);
  2. 標準錯誤輸出不使用完全緩存。

上面的標準顯然沒有具體說明各種情況,一般來說:

  1. 標準錯誤輸出不適用緩存;
  2. 其他流,如果關聯終端,則使用行緩存,否則使用完全緩存。

我們可以使用函數setbuf和setvbuf函數更改流的緩存機制。

函數聲明:

#include <stdio.h>

void setbuf(FILE* restrict fp, char* restrict buf);

int servbuf(FILE *restrict fp, char* restrict buf, int mode, size_t size);

函數返回值:

  • OK:0;
  • Error:非0

這些函數必須在流打開之后,其他流操作執行之前被調用。

函數作用:

setbuf可以打開或關閉緩存,打開緩存時,buf指向一個大小為BUFSIZ(stdio.h中定義的宏)的buffer,通常打開的時完全緩存,如果當前流關聯的是終端設備,有的系統也會使用行緩存;

servbuf可以指定打開哪種類型的緩存。mode的參數可以取如下的值,如果指定為無緩存,則參數buf和size都會被忽略。

NewImage

函數行為總結如下表所示:

NewImage

通常來說,我們應該讓系統自己選擇buffer大小并自動分配,這樣標準IO庫會在關閉流時自動釋放該內存。

?

flush函數。

函數聲明:

#include <stdio.h>

int fflush(FILE *fp);

函數作用:

使得該流的所有緩存中未寫入硬盤的數據傳入內核中。

一種特殊情況是,如果fp為NULL,fflush會使得所有緩存的數據都被flush。

?

3 打開流(opening a stream)

函數fopen、freopen和fdopen函數用來打開一個標準輸入輸出流。

函數聲明:

#include <stdio.h>

FILE *fopen(const char *restrict pathname, const char* restrict type);

FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);

FILE *fdopen(int fd, const char *type);

函數細節:

  • 函數fopen打開指定的文件;
  • 函數freopen函數打開指定的文件到指定的流上,如果該流已經被打開,則先關閉該流;如果之前已經被打開的流設置了orientation,則清理。函數freopen通常用來打開文件到預定義的流上,如標準輸入,標準輸出或標準錯誤輸出;
  • fdopen輸入一個文件描述符,將描述符關聯到一個標準IO流上。函數fdopen的作用主要是為了將管道和網絡連接關聯到一個流上,而這些特殊類型的文件不能使用fopen函數打開,我們必須先用特定的函數獲取文件描述符,然后用fdopen函數關聯到一個流上。

參數type取值如下表所示,一共有15種取值,有得取值作用相同:

NewImage

表格說明:

  • 參數中的b字符為了讓標準IO系統區分文本文件(text file)和二進制文件(binary file),因為內核并不區分文件文件和二進制文件,所以b字符并不影響內核的行為。
  • 函數fdopen的參數type和其他的稍有不同。因為文件描述符已經被打開,所以打開文件流并不截斷文件至長度為0。
  • 標準IO庫函數的append模式不可以用來創建新文件,因為要得到一個文件描述符,必須先打開一個存在的文件。
  • 同樣支持多進程同時以append模式寫同一個文件。

當打開一個流對文件進行讀寫時,有兩個限制:

  • 輸入后,如果不調用函數fflush, fseek, fsetpos或rewind的話,不可以緊接著進行輸出。
  • 輸出后,如果不調用該函數fseek, fsetpos或rewind的話,不可以緊接著進行輸入。

六種方式打開一個流總結如下表所示:

NewImage

需要注意的一點是,當以w和a模式創建一個新文件時,并不能像open或create函數一樣指定文件的權限標志位。

一種解決方法是通過調整我們的umask。

打開的流默認的是完全緩存,如果該流關聯的是終端設備,則是行緩存。

像之前提到的那樣,我們打開了一個流,并在其他操作之前,可以調用setbuf或setvbuf函數修改緩存方式。

關閉流

函數聲明:

#include <stdio.h>

int fclose(FILE* fp);

函數細節,關閉流之前:

  • 所有緩存待輸出的數據都會被輸出;
  • 所有緩存帶輸入的數據都會被丟棄;
  • 如果流使用的緩存是由標準IO庫分配,則緩存會被釋放;
  • 如果進程正常終止,則所有緩存數據都會被flush(輸出或者寫入硬盤),并且所有打開的流都會被關閉。
4 讀寫一個流(Reading and Writing a Stream)?

當我們打開一個流,我們有三種讀寫方式可供選擇:

  • 一次一個字符讀寫
  • 一次一行讀寫:使用函數fgets和fputs
  • 直接讀寫:每次讀寫固定長度的數據,使用函數fread和fwrite。
輸入函數

函數聲明:

#include <stdio.h>

int getc(FILE* fp);

int fgetc(FILE* fp);

int getchar(void);

函數返回值:

  • ok:下一個字符
  • EOF:文件結尾,一般為-1
  • error:負數

函數細節:

  • getchar和getc不同的地方在于:前者一定實現為函數,而后者可以被實現為一個宏;
  • 函數返回值將unsigned char轉型為int,這里,unsigned是為了轉型為int時不會是負數。返回整數的目的是為了讓所有可能的值都可以返回,包括錯誤碼和文件結尾;
  • 文件結尾符EOF往往定義為負數,而錯誤碼也是負數,因此我們無法從返回值上判斷是到達了文件結尾還是報錯。
  • 為了區分上面的兩種情況,我們需要調用函數ferror或者feof。

?

函數聲明:

#include <stdio.h>

int ferror(FILE* fp);

int feof(FILE* fp); ? ?// Both return: nonzero(true) if condition is true, 0(false) otherwise

void clearerr(FILE* fp);

在大多的實現中,FILE對象中會維護兩個flag:

  • 一個error flag
  • 一個文件結尾符flag

這兩個flag都可以通過調用clearerr清空。

?

讀取一個流后,我們可以調用函數ungetc壓回讀出來的字符。

函數聲明:

#include <stdio.h>

int ungetc(int c, FILE* fp);

函數返回值:c if OK, EOF on error

函數細節:只支持單個個字符的壓回。

使用場景:

壓回操作常使用在下面的場景:對于一個輸入流,我們需要根據下一個字符來判斷該如何處理當前的字符。

?

輸出函數

輸出函數和我們討論過的輸入函數一一對應,不再贅述。

函數聲明:

#include <stdio.h>

int putc(int c, FILE* fp);

int fputc(int c, FILE* fp);

int putchar(int c);

?

5 逐行輸入輸出操作(Line-at-a-Time IO)

函數fgets和gets提供了逐行輸入功能。

函數聲明:

#include <stdio.h>

char *fgets(char* restrict buf, int n, FILE* restrict fp);

char *gets(char* buf);

函數細節:

  • 兩個函數都是讀取一行數據至buffer中。
  • 函數gets從標準輸入流中讀取,fgets從指定的輸入流中讀取。
  • fgets需要我們指定緩沖區大小,讀入的一行數據不得多于n-1個字符,以NULL結尾。如果fgets讀取該行數據長度大于n,則該次只讀取n-1個字符,并以null結尾,剩余的字符在下次調用fgets時讀入。
  • gets函數不推薦使用,因為它不做越界檢查。

函數fputs和puts提供了逐行輸出的功能。

函數聲明:

#include <stdio.h>

int fputs(const char* restrict str, FILE* restrict fp);

int puts(const char* str);

函數細節:

  • fputs函數將一個以null結尾的字符串輸出到指定流中,最后的null byte并不輸出;
  • puts函數同樣會輸出一個以null結尾的字符串到標準輸出,最后的null byte并不輸出,輸出結束后會輸出一個換行符;
  • 所以我們也不推薦使用puts函數,防止自動輸出一個換行符,但是我們在使用fputs時要記得在必要的時候自己處理換行符。

?

6 標準輸入輸出效率分析

比較標準:

將一定量的數據從標準輸入拷貝到標準輸出,計算這一過程所需要的

  • 用戶CPU時間(User CPU)
  • 系統CPU時間(System CPU)
  • Clock time
  • 程序文本大小

Code:

使用getc和putc的版本:

#include "apue.h"

?

int

main(void)

{

? ? int ? ? c;

?

? ? while ((c = getc(stdin)) != EOF)

? ? ? ? if (putc(c, stdout) == EOF)

? ? ? ? ? ? err_sys("output error");

?

? ? if (ferror(stdin))

? ? ? ? err_sys("input error");

?

? ? exit(0);

}

使用fgets和fputs的版本:

#include "apue.h"

?

int

main(void)

{

? ? char? ? buf[MAXLINE];

?

? ? while (fgets(buf, MAXLINE, stdin) != NULL)

? ? ? ? if (fputs(buf, stdout) == EOF)

? ? ? ? ? ? err_sys("output error");

?

? ? if (ferror(stdin))

? ? ? ? err_sys("input error");

?

? ? exit(0);

}

測試數據:95.8M 3百萬行

測試結果(和第三章中的數據進行了對比,之前跳過了該章節,可以自行查看一下):

NewImage

結果說明:

  • 可以發現標準IO庫函數User CPU時間都比read版本的最好時間要大,因為逐字符讀寫需要執行100million次循環,逐行讀寫需要執行3百萬次循環,而第一行使用的read的最有版本執行了25224次循環;
  • clock time的差異原因在于用戶態時間的差異和等待IO完成的時間上的差異;
  • System CPU時間基本和之前版本的相同,因為內核請求數基本相同。因此,在不關心buffer大小和分配,或者只需要關心一行buffer大小的使用下,獲取了幾乎最優的buffer選擇。
  • 最后一列顯示了編譯器編譯后生成的匯編文件的大小。
  • 逐行讀寫比逐字符讀寫快得多,因為fgets和fputs是用memccpy實現,memccpy函數用匯編來實現,效率更高。
  • fgetc版本比read版本的最差時間(BUFFSIZE=1)要快得多,原因在于read版本會執行200million次函數調用,由于無緩存機制,所以相應的也會執行200million次系統調用,而fgetc版本也會執行200million次函數調用,但是由于緩存機制,只需要執行25224次系統調用。我們知道,系統調用的開銷要比函數調用大得多。

?

?

7 小結

標準IO函數庫分為兩篇來介紹,本篇是第一篇,主要介紹了

  • 流的基本概念
  • 流的基本操作,包括打開、關閉、讀寫
  • 對比了使用標準IO庫的讀寫效率

?

?

參考資料:

《Advanced PRogramming in the UNIX Envinronment 3rd》?

?


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 圆产精品久久久久久久久久久 | 91精品久久久久久久 | 欧美精品一二三区 | 一级外国毛片 | 欧美性生交xxxxx免费观看 | 国产超碰人人爽人人做人人爱 | 色羞羞 | av在线免费观看网 | 免费久久久久 | 欧美亚洲国产一区 | jj视频在线播放 | 免费在线观看成人av | 精品亚洲网站 | 久久久久夜色精品国产老牛91 | 免费视频一区 | 亚洲最新黄色网址 | 欧美69free性videos | 人人做人人看 | 72pao成人国产永久免费视频 | 一级做人爱c黑人影片 | 99在线免费观看视频 | 亚洲精品欧美二区三区中文字幕 | 羞羞电影在线观看 | 日韩美女电影 | 天天鲁在线视频免费观看 | 日韩精品中文字幕在线观看 | 国产精品成人一区二区三区吃奶 | 91精品国产综合久久青草 | 在线成人免费视频 | 国产一级毛片高清视频 | 欧美一级免费在线观看 | 欧美成人三级视频 | 羞羞网站在线观看入口免费 | 免费观看欧美一级片 | 国产精品久久久久久久亚洲按摩 | 精品国产一区二区三区在线观看 | 欧美日韩国产成人在线观看 | 亚洲第一成av人网站懂色 | 亚州综合一区 | 成码无人av片在线观看网站 | 免费亚洲视频在线观看 |