国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Unix > 正文

UNIX高級(jí)環(huán)境編程(6)標(biāo)準(zhǔn)IO函數(shù)庫(kù)

2024-06-28 13:21:46
字體:
供稿:網(wǎng)友
UNIX高級(jí)環(huán)境編程(6)標(biāo)準(zhǔn)IO函數(shù)庫(kù) - 流的概念和操作

標(biāo)準(zhǔn)IO函數(shù)庫(kù)隱藏了buffer大小和分配的細(xì)節(jié),使得我們可以不用關(guān)心預(yù)分配的內(nèi)存大小是否正確的問題。

雖然這使得這個(gè)函數(shù)庫(kù)很容易用,但是如果我們對(duì)函數(shù)的原理不熟悉的話,也容易遇到很多問題。

?

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

前面的章節(jié)中,IO集中在文件描述符,每一個(gè)打開的文件都對(duì)應(yīng)一個(gè)文件描述符,通過文件描述符對(duì)文件進(jìn)行操作。

現(xiàn)在使用了標(biāo)準(zhǔn)IO庫(kù),討論的重點(diǎn)集中在流(streams)。

簡(jiǎn)要了解一下流:

  • 當(dāng)我們打開或創(chuàng)建了一個(gè)文件,我們說我們有一個(gè)流和該文件關(guān)聯(lián)。
  • stream支持單字節(jié)字符集和多字節(jié)字符集。stream的屬性orientation決定使用單字符集還是多字符集。
  • 當(dāng)一個(gè)stream被創(chuàng)建時(shí),沒有指定orientation,這時(shí),當(dāng)使用寬字符集IO函數(shù)時(shí),流的orientation設(shè)置為支持寬字符集;當(dāng)使用單字符集IO函數(shù)時(shí),流的orientation設(shè)置為支持單字符集。

只有兩個(gè)函數(shù)可以修改流的orientation:

  • freopen會(huì)清除流的orientation;
  • fwide用來設(shè)置流的orientation。

fwide函數(shù)聲明:

#include <stdio.h>

#include <wchar.h>

int fwide(FILE* fp, int mode);

函數(shù)返回值:

  • 返回整數(shù)表示支持多字節(jié)字符集;
  • 返回負(fù)數(shù)表示支持單字節(jié)字符集;
  • 返回0表示沒有設(shè)置stream的orientation。

mode取值的不同決定函數(shù)fwide的不同的行為:

  • 如果mode為負(fù)數(shù),fwide試著設(shè)置指定流支持單字節(jié)字符集;
  • 如果mode為整數(shù),fwide試著設(shè)置指定流支持多字節(jié)字符集;
  • 如果mode為0,fwide不會(huì)試著設(shè)置流的orientation,但是會(huì)返回一個(gè)值代表當(dāng)前流的orientation。

當(dāng)我們打開一個(gè)流,函數(shù)fopen返回一個(gè)指向FILE對(duì)象的指針。FILE對(duì)象通常是一個(gè)結(jié)構(gòu)體,包含所有控制流所需要的信息,包括:

  • 實(shí)際IO所用的文件描述符;
  • 一個(gè)指向流所使用的buffer的指針;
  • buffer的大小;
  • 當(dāng)前在buffer中的字符數(shù);
  • error flag;
  • 等。

?

2 緩存(Buffering)

緩存(buffering)的作用是為了盡可能少地調(diào)用read和write系統(tǒng)調(diào)用。

標(biāo)準(zhǔn)IO庫(kù)提供三種類型的buffering:

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

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

flush:標(biāo)準(zhǔn)IO緩存中內(nèi)容立刻寫入硬盤或者輸出。在終端設(shè)備中,flush的作用也可能是丟棄緩存中得數(shù)據(jù)。

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

ISO C標(biāo)準(zhǔn)要求下面的緩存特性:

  1. 標(biāo)準(zhǔn)輸入輸出在不關(guān)聯(lián)交互設(shè)備的請(qǐng)款下,使用完全緩存(fully buffered);
  2. 標(biāo)準(zhǔn)錯(cuò)誤輸出不使用完全緩存。

上面的標(biāo)準(zhǔn)顯然沒有具體說明各種情況,一般來說:

  1. 標(biāo)準(zhǔn)錯(cuò)誤輸出不適用緩存;
  2. 其他流,如果關(guān)聯(lián)終端,則使用行緩存,否則使用完全緩存。

我們可以使用函數(shù)setbuf和setvbuf函數(shù)更改流的緩存機(jī)制。

函數(shù)聲明:

#include <stdio.h>

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

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

函數(shù)返回值:

  • OK:0;
  • Error:非0

這些函數(shù)必須在流打開之后,其他流操作執(zhí)行之前被調(diào)用。

函數(shù)作用:

setbuf可以打開或關(guān)閉緩存,打開緩存時(shí),buf指向一個(gè)大小為BUFSIZ(stdio.h中定義的宏)的buffer,通常打開的時(shí)完全緩存,如果當(dāng)前流關(guān)聯(lián)的是終端設(shè)備,有的系統(tǒng)也會(huì)使用行緩存;

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

NewImage

函數(shù)行為總結(jié)如下表所示:

NewImage

通常來說,我們應(yīng)該讓系統(tǒng)自己選擇buffer大小并自動(dòng)分配,這樣標(biāo)準(zhǔn)IO庫(kù)會(huì)在關(guān)閉流時(shí)自動(dòng)釋放該內(nèi)存。

?

flush函數(shù)。

函數(shù)聲明:

#include <stdio.h>

int fflush(FILE *fp);

函數(shù)作用:

使得該流的所有緩存中未寫入硬盤的數(shù)據(jù)傳入內(nèi)核中。

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

?

3 打開流(opening a stream)

函數(shù)fopen、freopen和fdopen函數(shù)用來打開一個(gè)標(biāo)準(zhǔn)輸入輸出流。

函數(shù)聲明:

#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);

函數(shù)細(xì)節(jié):

  • 函數(shù)fopen打開指定的文件;
  • 函數(shù)freopen函數(shù)打開指定的文件到指定的流上,如果該流已經(jīng)被打開,則先關(guān)閉該流;如果之前已經(jīng)被打開的流設(shè)置了orientation,則清理。函數(shù)freopen通常用來打開文件到預(yù)定義的流上,如標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出或標(biāo)準(zhǔn)錯(cuò)誤輸出;
  • fdopen輸入一個(gè)文件描述符,將描述符關(guān)聯(lián)到一個(gè)標(biāo)準(zhǔn)IO流上。函數(shù)fdopen的作用主要是為了將管道和網(wǎng)絡(luò)連接關(guān)聯(lián)到一個(gè)流上,而這些特殊類型的文件不能使用fopen函數(shù)打開,我們必須先用特定的函數(shù)獲取文件描述符,然后用fdopen函數(shù)關(guān)聯(lián)到一個(gè)流上。

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

NewImage

表格說明:

  • 參數(shù)中的b字符為了讓標(biāo)準(zhǔn)IO系統(tǒng)區(qū)分文本文件(text file)和二進(jìn)制文件(binary file),因?yàn)閮?nèi)核并不區(qū)分文件文件和二進(jìn)制文件,所以b字符并不影響內(nèi)核的行為。
  • 函數(shù)fdopen的參數(shù)type和其他的稍有不同。因?yàn)槲募枋龇呀?jīng)被打開,所以打開文件流并不截?cái)辔募灵L(zhǎng)度為0。
  • 標(biāo)準(zhǔn)IO庫(kù)函數(shù)的append模式不可以用來創(chuàng)建新文件,因?yàn)橐玫揭粋€(gè)文件描述符,必須先打開一個(gè)存在的文件。
  • 同樣支持多進(jìn)程同時(shí)以append模式寫同一個(gè)文件。

當(dāng)打開一個(gè)流對(duì)文件進(jìn)行讀寫時(shí),有兩個(gè)限制:

  • 輸入后,如果不調(diào)用函數(shù)fflush, fseek, fsetpos或rewind的話,不可以緊接著進(jìn)行輸出。
  • 輸出后,如果不調(diào)用該函數(shù)fseek, fsetpos或rewind的話,不可以緊接著進(jìn)行輸入。

六種方式打開一個(gè)流總結(jié)如下表所示:

NewImage

需要注意的一點(diǎn)是,當(dāng)以w和a模式創(chuàng)建一個(gè)新文件時(shí),并不能像open或create函數(shù)一樣指定文件的權(quán)限標(biāo)志位。

一種解決方法是通過調(diào)整我們的umask。

打開的流默認(rèn)的是完全緩存,如果該流關(guān)聯(lián)的是終端設(shè)備,則是行緩存。

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

關(guān)閉流

函數(shù)聲明:

#include <stdio.h>

int fclose(FILE* fp);

函數(shù)細(xì)節(jié),關(guān)閉流之前:

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

當(dāng)我們打開一個(gè)流,我們有三種讀寫方式可供選擇:

  • 一次一個(gè)字符讀寫
  • 一次一行讀寫:使用函數(shù)fgets和fputs
  • 直接讀寫:每次讀寫固定長(zhǎng)度的數(shù)據(jù),使用函數(shù)fread和fwrite。
輸入函數(shù)

函數(shù)聲明:

#include <stdio.h>

int getc(FILE* fp);

int fgetc(FILE* fp);

int getchar(void);

函數(shù)返回值:

  • ok:下一個(gè)字符
  • EOF:文件結(jié)尾,一般為-1
  • error:負(fù)數(shù)

函數(shù)細(xì)節(jié):

  • getchar和getc不同的地方在于:前者一定實(shí)現(xiàn)為函數(shù),而后者可以被實(shí)現(xiàn)為一個(gè)宏;
  • 函數(shù)返回值將unsigned char轉(zhuǎn)型為int,這里,unsigned是為了轉(zhuǎn)型為int時(shí)不會(huì)是負(fù)數(shù)。返回整數(shù)的目的是為了讓所有可能的值都可以返回,包括錯(cuò)誤碼和文件結(jié)尾;
  • 文件結(jié)尾符EOF往往定義為負(fù)數(shù),而錯(cuò)誤碼也是負(fù)數(shù),因此我們無法從返回值上判斷是到達(dá)了文件結(jié)尾還是報(bào)錯(cuò)。
  • 為了區(qū)分上面的兩種情況,我們需要調(diào)用函數(shù)ferror或者feof。

?

函數(shù)聲明:

#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);

在大多的實(shí)現(xiàn)中,F(xiàn)ILE對(duì)象中會(huì)維護(hù)兩個(gè)flag:

  • 一個(gè)error flag
  • 一個(gè)文件結(jié)尾符flag

這兩個(gè)flag都可以通過調(diào)用clearerr清空。

?

讀取一個(gè)流后,我們可以調(diào)用函數(shù)ungetc壓回讀出來的字符。

函數(shù)聲明:

#include <stdio.h>

int ungetc(int c, FILE* fp);

函數(shù)返回值:c if OK, EOF on error

函數(shù)細(xì)節(jié):只支持單個(gè)個(gè)字符的壓回。

使用場(chǎng)景:

壓回操作常使用在下面的場(chǎng)景:對(duì)于一個(gè)輸入流,我們需要根據(jù)下一個(gè)字符來判斷該如何處理當(dāng)前的字符。

?

輸出函數(shù)

輸出函數(shù)和我們討論過的輸入函數(shù)一一對(duì)應(yīng),不再贅述。

函數(shù)聲明:

#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)

函數(shù)fgets和gets提供了逐行輸入功能。

函數(shù)聲明:

#include <stdio.h>

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

char *gets(char* buf);

函數(shù)細(xì)節(jié):

  • 兩個(gè)函數(shù)都是讀取一行數(shù)據(jù)至buffer中。
  • 函數(shù)gets從標(biāo)準(zhǔn)輸入流中讀取,fgets從指定的輸入流中讀取。
  • fgets需要我們指定緩沖區(qū)大小,讀入的一行數(shù)據(jù)不得多于n-1個(gè)字符,以NULL結(jié)尾。如果fgets讀取該行數(shù)據(jù)長(zhǎng)度大于n,則該次只讀取n-1個(gè)字符,并以null結(jié)尾,剩余的字符在下次調(diào)用fgets時(shí)讀入。
  • gets函數(shù)不推薦使用,因?yàn)樗蛔鲈浇鐧z查。

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

函數(shù)聲明:

#include <stdio.h>

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

int puts(const char* str);

函數(shù)細(xì)節(jié):

  • fputs函數(shù)將一個(gè)以null結(jié)尾的字符串輸出到指定流中,最后的null byte并不輸出;
  • puts函數(shù)同樣會(huì)輸出一個(gè)以null結(jié)尾的字符串到標(biāo)準(zhǔn)輸出,最后的null byte并不輸出,輸出結(jié)束后會(huì)輸出一個(gè)換行符;
  • 所以我們也不推薦使用puts函數(shù),防止自動(dòng)輸出一個(gè)換行符,但是我們?cè)谑褂胒puts時(shí)要記得在必要的時(shí)候自己處理?yè)Q行符。

?

6 標(biāo)準(zhǔn)輸入輸出效率分析

比較標(biāo)準(zhǔn):

將一定量的數(shù)據(jù)從標(biāo)準(zhǔn)輸入拷貝到標(biāo)準(zhǔn)輸出,計(jì)算這一過程所需要的

  • 用戶CPU時(shí)間(User CPU)
  • 系統(tǒng)CPU時(shí)間(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);

}

測(cè)試數(shù)據(jù):95.8M 3百萬行

測(cè)試結(jié)果(和第三章中的數(shù)據(jù)進(jìn)行了對(duì)比,之前跳過了該章節(jié),可以自行查看一下):

NewImage

結(jié)果說明:

  • 可以發(fā)現(xiàn)標(biāo)準(zhǔn)IO庫(kù)函數(shù)User CPU時(shí)間都比read版本的最好時(shí)間要大,因?yàn)橹鹱址x寫需要執(zhí)行100million次循環(huán),逐行讀寫需要執(zhí)行3百萬次循環(huán),而第一行使用的read的最有版本執(zhí)行了25224次循環(huán);
  • clock time的差異原因在于用戶態(tài)時(shí)間的差異和等待IO完成的時(shí)間上的差異;
  • System CPU時(shí)間基本和之前版本的相同,因?yàn)閮?nèi)核請(qǐng)求數(shù)基本相同。因此,在不關(guān)心buffer大小和分配,或者只需要關(guān)心一行buffer大小的使用下,獲取了幾乎最優(yōu)的buffer選擇。
  • 最后一列顯示了編譯器編譯后生成的匯編文件的大小。
  • 逐行讀寫比逐字符讀寫快得多,因?yàn)閒gets和fputs是用memccpy實(shí)現(xiàn),memccpy函數(shù)用匯編來實(shí)現(xiàn),效率更高。
  • fgetc版本比read版本的最差時(shí)間(BUFFSIZE=1)要快得多,原因在于read版本會(huì)執(zhí)行200million次函數(shù)調(diào)用,由于無緩存機(jī)制,所以相應(yīng)的也會(huì)執(zhí)行200million次系統(tǒng)調(diào)用,而fgetc版本也會(huì)執(zhí)行200million次函數(shù)調(diào)用,但是由于緩存機(jī)制,只需要執(zhí)行25224次系統(tǒng)調(diào)用。我們知道,系統(tǒng)調(diào)用的開銷要比函數(shù)調(diào)用大得多。

?

?

7 小結(jié)

標(biāo)準(zhǔn)IO函數(shù)庫(kù)分為兩篇來介紹,本篇是第一篇,主要介紹了

  • 流的基本概念
  • 流的基本操作,包括打開、關(guān)閉、讀寫
  • 對(duì)比了使用標(biāo)準(zhǔn)IO庫(kù)的讀寫效率

?

?

參考資料:

《Advanced PRogramming in the UNIX Envinronment 3rd》?

?


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 白朗县| 武强县| 故城县| 克什克腾旗| 永德县| 马鞍山市| 阿克陶县| 崇明县| 万山特区| 孟村| 孙吴县| 乐东| 大港区| 兴仁县| 闽清县| 白银市| 德钦县| 秭归县| 秦安县| 大石桥市| 辽阳县| 资源县| 探索| 鹿邑县| 肥乡县| 崇义县| 鹤岗市| 合肥市| 舞钢市| 龙海市| 方正县| 涞源县| 陕西省| 如皋市| 汾阳市| 新建县| 合肥市| 陈巴尔虎旗| 钦州市| 吐鲁番市| 黔江区|