Linux I/O總結


文件流

標准I/O文件流可用於單字節或多字節字符集。流的定向決定了所讀寫的是單字節還是多字節。流在最初創建時,並沒有定向,此時如果在為定向的流上使用多字節I/O函數,那么該流被設置為寬定向的;如果在為定向的流中使用單字節I/O函數,那么該流被設置為字節定向的。

如下兩個函數可用於改變流的定向:

#include <stdio.h>

#include <wchar.h>

int fwide(FILE* fp, int mode);

返回值:流為寬定向,返回正值;流為字節定向,返回負值;流為定向,返回0

說明:

fwide不改變已定向的流的定向。

mode > 0,則fwide試圖將流設置為寬定向;

mode < 0,則fwide試圖將流設置為字節定向;

mode = 0,則fwide不設置流的定向,而是返回標識當前流定向的值。

 

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

說明:

在指定的流上打開一個指定的文件,如果該流已經打開,則先關閉該流;若該流已經定向,則清除該定向。該函數常用於將一個指定的文件打開為一個預定義的流:標准輸入、標准輸出、標准錯誤

 

緩沖

標准I/O提供緩沖的目的是盡可肯地減少read和write的次數,有3種類型:

類型

說明

全緩沖

在填滿整個緩沖區后才進行實際的I/O操作。可以調用fflush函數將緩沖區的數據寫入磁盤中。

行緩沖

當在輸入和輸出中遇到換行符或者緩沖區已滿時,執行實際的I/O操作。

無緩沖

標准I/O不對字符進行緩沖存儲。標准錯誤流stderr通常不帶緩沖區,這就使得可以盡快顯示出錯信息。

對於一個給定的流,也可以使用以下函數更改系統默認的緩沖類型:

#include <stdio.h>

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

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

返回值:成功,0;失敗,-1

說明:

setbuf用於打開或關閉流緩沖機制,參數buf指向一個長度為BUFSIZ(該常量在<stdio.h>中定義)的緩沖區;如果要關閉緩沖,則將buf設置為NULL即可。

setvbuf用於精確地設置所需的緩沖類型,mode取值如下:_IOFBF(全緩沖)/_IOLBF(行緩沖)/_IONBF(無緩沖);如果指定了mode為帶緩沖類型,而buf卻為NULL,則系統會自動分配BUFSIZ個字節的緩沖區。

 

int fflush(FILE* fp);

說明:

強制沖刷一個流到磁盤中。如果fp為NULL,則系統中的所有輸出流將被沖刷。

 

打開流

#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打開指定路徑pathname的文件;freopen用於將一個指定文件打開為一個預定義的流:標准輸入、標准輸出、標准錯誤;fdopen根據fd返回一個打開的流,常用於由創建管道和網絡通信函數返回的描述符。

type參數指定了對該I/O流的讀寫方式。

 

讀寫流

在打開流后,有3種不同類型的非格式化I/O(格式化I/O是諸如printf和scanf等的函數):

類型

說明

每次處理一個字符

#include <stdio.h>

int getc(FILE* fp);

int fgetc(FILE* fp);

int getchar(void);

返回值:成功,返回下一個字符;失敗或到達文件尾端,返回EOF

說明:

getchar等價於getc(stdin)。

getc可以被實現為宏,而fgetc一定是個函數,因此fgetc調用的時間通常長於getc。

 

為了區分引起EOF的原因是到達文件尾端還是讀入失敗,引入如下三個函數:

int ferror(FILE* fp);

int feof(FILE* fp);

void clearerr(FILE* fp);

返回值:條件為真,非0;否則,0

說明:

每個流在FILE對象中維護了兩個標志:出錯標志、文件結束標志,調用clearerr可以清除這兩個標志。

 

有時在讀一個輸入流時,我們需要先查看下一個字符是否是需要讀入的字符,此時需要利用ungetc函數將字符壓回輸入流中:

int ungetc(int c, FILE* fp);

返回值:成功,返回c;失敗,EOF

說明:

用ungetc壓回字符,並非將其寫到底層文件或設備中,只是將其寫回標准I/O的緩沖區中。

 

int putc(int c, FILE* fp);

int fputc(int c, FILE* fp);

int putchar(int c);

返回值:成功,返回c;失敗,EOF

說明:

putchar(c)等價於putc(c, stdout)。

putc可被實現為宏,而fputc只能是一個函數。

 

每次處理一行

#include <stdio.h>

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

char* gets(char* buf);

返回值:成功,返回buf;失敗,返回NULL

說明:

gets從標准輸入讀,fgets從指定的文件讀。

每次讀取直至遇到換行符或者達到緩沖區長度n(由於緩沖區以null字節結尾,有效長度實則是n - 1),如果fgets讀取的行超過n – 1個字節,那么返回前n – 1個字節,對fgets的下一次調用將繼續讀取該行其余部分。

gets由於不能指定緩沖區長度n,因此在最新的ISO C中已被忽略,因此不推薦使用gets函數。

 

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

int puts(const char* str);

返回值:成功,返回非負值;失敗,返回EOF

說明:

fputs將一個以null字節終止的字符串寫到指定的流中,null終止符不寫出。

雖然,puts並沒有太大的安全隱患,但還是避免使用它,因為puts每次寫出數據后還附加寫出一個換行符。

直接I/O或二進制I/O

比如我們需要一次讀寫一個完整的結構,如果使用getc/putc,那么必須循環遍歷整個結構,每次處理一個字節,非常麻煩;如果使用fgets/fputs,那么由於其遇到null字節就停止了,而在結構中可能含有null字節,因此也不能很好地工作。因此,提供了如下兩個函數:

#include <stdio.h>

size_t fread(void* restrict ptr, size_t size, size_t nobj, FILE* restrict fp);

size_t fwrite(const void* restrict ptr, size_t size, size_t nobj, FILE* restrict fp);

返回值:成功讀寫的結構個數

例如:將data數組的第2~5個元素(一共4個)寫至一個文件上:

float data[10];

if(fwrite(&data[2], sizeof(float), 4, fp) != 4)

    printf(“fwrite error”);

示例程序:將標准輸入復制到標准輸出:
1)每次處理一個字符(getc/putc) [root@benxintuzi IO]# cat getc.c
#include <stdio.h>

int main(void)
{
        int c;

        while((c = getc(stdin)) != EOF)
                if(putc(c, stdout) == EOF)
                        printf("output error\n");

        if(ferror(stdin))
                printf("input error\n");

        return 0;
}

[root@benxintuzi IO]# ./getc 1 hello
1 hello
2 benxintuzi
2 benxintuzi
^C
[root@benxintuzi IO]# (2) 每次處理一行(fgets/fputs) [root@benxintuzi IO]# cat fgets.c
#include <stdio.h>

#define MAXLINE 4096

int main(void)
{
        char buf[MAXLINE];

        while(fgets(buf, MAXLINE, stdin) != NULL)
                if(fputs(buf, stdout) == EOF)
                        printf("output error\n");

        if(ferror(stdin))
                printf("input error\n");

        return 0;
}

[root@benxintuzi IO]# ./fgets
benxin
benxin
tuzi
tuzi
^C
[root@benxintuzi IO]#

 

流定位

#include <stdio.h>

long ftell(FILE* fp);

offset ftello(FILE* fp);

返回值:成功,返回文件當前位置;失敗,返回-1L

 

int fseek(FILE* fp, long offset, int whence);

int fseeko(FILE* fp, off_t offset, int whence);

返回值:成功,0;失敗,-1

說明:

whence取值如下:SEEK_SET/SEEK_CUR/SEEK_END。

fseek和fseeko唯一的區別是offset的類型不同。

 

void rewind(FILE* fp)將一個流設置到文件的起始位置。

 

int fgetpos(FILE* restrict fp, fpos_t* restrict pos);

int fsetpos(FILE* fp, const fpos_t* pos);

返回值:成功,0;失敗,-1

說明:

fgetpos將當前文件指針存入pos中;fsetpos將pos的值設為當前文件位置。

 

格式化I/O

格式化輸出:

#include <stdio.h>

int printf(const char* restrict format, ...);

int fprintf(FILE* restrict fp, const char* restrict format, ...);

int dprintf(int fd, const char* restrict format, ...);

int sprintf(char* restrict buf, const char* restrict format, ...);

int snprintf(char* restrict buf, size_t n, const char* restrict format, ...);

返回值:成功,返回寫入的字符數;失敗,返回負值

說明:

printf寫到標准輸出,fprintf寫到文件,dprintf寫到文件描述符,sprintf寫到buf中,但是可能會溢出,snprintf也寫到buf中,但是由於指定了緩沖區長度n,可能截斷但不會溢出。

 

格式化輸入:

#include <stdio.h>

int scanf(const char* restrict format, ...);

int fscanf(FILE* restrict fp, const char* restrict format, ...);

int sscanf(const char* restrict buf, const char* restrict format, ...);

 

格式控制:

%[flags][fldwidth][precision][lenmodifier]convtype

flags

說明

'

將整數按千位分組輸出

-

左對齊輸出

+

顯示帶符號轉換的正負號

空格

如果第一個字符不是正負號,則用空格代替

#

指定轉換格式,例如0x前綴

0

用0而非空格進行填充

 

fldwidth最小字段寬度。若轉換后字符數小於該值,則用空格填充。該值可以是一個非負整數或*。

precision精度表示,整數位數、浮點數小數位數、字符串最大字節數。該值可以是一個.加上非負整數或者*。

lenmodifier參數長度。hh(signed或unsigned char)/h(signed或unsigned short)/l(signed或unsigned long)/ll(signed或unsigned long long)/j(intmax_t或uintmax_t)/z(size_t)/t(ptrdiff_t)/L(long double)。

convtype轉換類型:

d、i

有符號十進制

o

無符號八進制

u

無符號十進制

x、X

無符號十六進制

f、F

雙精度浮點數

e、E

指數格式雙精度浮點數

g、G

根據轉換后的值解釋為f、F、e、E

a、A

十六進制指數格式雙精度浮點數

c

字符

s

字符串

C

寬字符

S

寬字符串

%

%本身

p

void*指針

 

臨時文件

每個標准I/O流都有一個相關聯的文件描述符,可以調用int fileno(FILE* fp)來獲得這個描述符。如下兩個函數用於創建臨時文件:

#inlcude <stdio.h>

char* tmpnam(char* ptr);

FILE* tmpfile(void);

說明:

tmpnam產生一個與現有文件名不同的臨時文件名,每次調用時,都產生一個不同的臨時文件名。最多的調用次數為TMP_MAX(定義在<stdio.h>中)。若ptr為NULL,則所產生的臨時文件名存放在一個靜態區中,指向該靜態區的指針作為函數值返回;如果ptr不為NULL,則其指向長度大於等於L_tmpnam個字符的數組,所產生的臨時文件名存放在該數組中,ptr作為函數值返回。

tmpfile創建一個臨時的二進制文件(wb+),在關閉文件或程序結束時自動刪除該文件。

 

#include <stdlib.h>

char* mkdtemp(char* template);

int mkstemp(char* template);

說明:

mkdtemp創建一個目錄,返回指向目錄名的指針;mkstemp創建一個文件,返回文件描述符。

template的后六位設置為XXXXXX。函數將這些占位符替換成不同的字符來構建一個唯一的名稱。

 

[root@benxintuzi IO]# cat tmp.c
#include <stdio.h>

int main(void)
{
        char name[L_tmpnam], line[4096];
        FILE* fp;

        printf("%s\n", tmpnam(NULL));                   /* first temp name */

        tmpnam(name);                                   /* second temp name */
        printf("%s\n", name);

        if((fp = tmpfile()) == NULL)                    /* create temp file */
                printf("tmpfile error\n");
        fputs("write one line to tmpfile\n", fp);       /* write to temp file */

        rewind(fp);                                     /* then read it back */
        if(fgets(line, sizeof(line), fp) == NULL)
                printf("fgets error\n");
        fputs(line, stdout);                            /* print the line */

        return 0;
}
[root@benxintuzi IO]# gcc tmp.c -o tmp /tmp/cc6sXVXs.o: In function `main': tmp.c:(.text+0x14): warning: the use of `tmpnam' is dangerous, better use `mkstemp' [root@benxintuzi IO]# ./tmp /tmp/filekyJuQu
/tmp/fileKA5UyL
write one line to tmpfile
[root@benxintuzi IO]#
[root@benxintuzi IO]# cat mkstemp.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

void make_temp(char *template);

int main(void)
{
        char    good_template[] = "/tmp/dirXXXXXX";     /* right way */
        char    *bad_template = "/tmp/dirXXXXXX";       /* wrong way*/

        printf("trying to create first temp file...\n");
        make_temp(good_template);
        printf("trying to create second temp file...\n");
        make_temp(bad_template);
        exit(0);
}

void make_temp(char *template)
{
        int                     fd;
        struct stat     sbuf;

        if ((fd = mkstemp(template)) < 0)
                printf("can't create temp file\n");
        printf("temp name = %s\n", template);
        close(fd);
        if (stat(template, &sbuf) < 0) {
                if (errno == ENOENT)
                        printf("file doesn't exist\n");
                else
                        printf("stat failed\n");
        } else {
                printf("file exists\n");
                unlink(template);
        }
}

[root@benxintuzi IO]# gcc mkstemp.c -o mkstemp [root@benxintuzi IO]# ./mkstemp
trying to create first temp file...
temp name = /tmp/dirnemrcT
file exists
trying to create second temp file...
Segmentation fault (core dumped)
[root@benxintuzi IO]#

說明:
對於第一個模板,由於使用了數組,則數組內容存儲在棧上;但是第二個模板,只有指針本身存儲在棧上,而具體字符串內容卻存放在可執行文件的只讀段中,因此當mkstemp函數試圖修改字符串時,出現segment fault。

 

內存流

在內存流中,所有的I/O都是通過在緩沖區與主存之間來回傳送字節來完成的。由於避免了緩沖區溢出,因此內存流非常適用於創建字符串。內存流只訪問主存,不訪問磁盤上的文件,所以性能方面會有顯著的提升。有三個函數用於內存流的創建:

#include <stdio.h>

FILE* fmemopen(void* restrict buf, size_t size, const char* restrict type);

返回值:成功,返回流指針;失敗,返回NULL

說明:

fmemopen函數允許調用者指定自己的緩沖區用於內存流。buf指向緩沖區的開始位置,size指定了緩沖區的大小,type參數控制流的使用方式:r/rb/w/wb/a/ab/r+/r+b/rb+/w+/w+b/wb+/a+/a+b/ab+

注意點:

(1)以追加方式打開內存流時,當前文件位置設為緩沖區中的第一個null字節;如果緩沖區中不存在null字節,則當前文件位置設為緩沖區結尾的后一個字節。

(2)以其他方式打開內存流時,當前文件位置設為緩沖區的開始位置。

(3)如果buf為null,則打開內存流沒有任何意義。

(4)增加流緩沖區中的數據或者調用fclose、fflush、fseek、fseeko、fsetpos時都會在當前位置寫入一個null字節。

 

#include <stdio.h>

FILE* open_menstream(char** bufp, size_t* sizep);

#include <wchar.h>

FILE* open_wmemstream(wchar_t** bufp, size_t sizep);

返回值:成功,返回流指針;失敗,返回NULL

說明:

open_memstream函數創建的流是面向字節的,其與fmemopen的區別如下:

創建的流只能寫打開;

不能指定自己的緩沖區,可以通過bufp和sizep訪問緩沖區地址和大小;

關閉流后需要自行釋放緩沖區;

對流添加字節會增加緩沖區的大小。

 

以下程序說明了如何在我們自己提供的緩沖區上操作內存流:

[root@benxintuzi IO]# cat memstr.c
#include <stdio.h>
#include <stdlib.h>

#define BSZ 48

int main(void)
{
        FILE *fp;
        char buf[BSZ];

        memset(buf, 'a', BSZ-2);
        buf[BSZ-2] = '\0';
        buf[BSZ-1] = 'X';
        if ((fp = fmemopen(buf, BSZ, "w+")) == NULL)
                printf("fmemopen failed\n");
        printf("initial buffer contents: %s\n", buf);
        fprintf(fp, "hello, world");
        printf("before flush: %s\n", buf);
        fflush(fp);
        printf("after fflush: %s\n", buf);
        printf("len of string in buf = %ld\n", (long)strlen(buf));

        memset(buf, 'b', BSZ-2);
        buf[BSZ-2] = '\0';
        buf[BSZ-1] = 'X';
        fprintf(fp, "hello, world");
        fseek(fp, 0, SEEK_SET);
        printf("after  fseek: %s\n", buf);
        printf("len of string in buf = %ld\n", (long)strlen(buf));

        memset(buf, 'c', BSZ-2);
        buf[BSZ-2] = '\0';
        buf[BSZ-1] = 'X';
        fprintf(fp, "hello, world");
        fclose(fp);
        printf("after fclose: %s\n", buf);
        printf("len of string in buf = %ld\n", (long)strlen(buf));

        return(0);
}

[root@benxintuzi IO]# ./memstr
                                            # 用a字符改寫緩沖區
initial buffer contents:                                          # fmemopen在緩沖區開始處放置null字節
before flush:                                                            # 流沖刷后緩沖區才會變化
after fflush: hello, world
len of string in buf = 12                                              # null字節加到字符串結尾
                                            # 現在用b字符改寫緩沖區
after  fseek: bbbbbbbbbbbbhello, world                                  # fseek引起緩沖區沖刷
len of string in buf = 24                               # 再次追加寫null字節
after fclose: hello, worldcccccccccccccccccccccccccccccccccc          # 現在用c字符改寫緩沖區
len of string in buf = 46                                                # 沒有追加寫null字節
[root@benxintuzi IO]#

 


免責聲明!

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



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