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

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

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

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

        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
", tmpnam(NULL));                   /* first temp name */

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

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

        rewind(fp);                                     /* then read it back */
        if(fgets(line, sizeof(line), fp) == NULL)
                printf("fgets error
");
        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...
");
        make_temp(good_template);
        printf("trying to create second temp file...
");
        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
");
        printf("temp name = %s
", template);
        close(fd);
        if (stat(template, &sbuf) < 0) {
                if (errno == ENOENT)
                        printf("file doesn't exist
");
                else
                        printf("stat failed
");
        } else {
                printf("file exists
");
                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] = '';
        buf[BSZ-1] = 'X';
        if ((fp = fmemopen(buf, BSZ, "w+")) == NULL)
                printf("fmemopen failed
");
        printf("initial buffer contents: %s
", buf);
        fprintf(fp, "hello, world");
        printf("before flush: %s
", buf);
        fflush(fp);
        printf("after fflush: %s
", buf);
        printf("len of string in buf = %ld
", (long)strlen(buf));

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

        memset(buf, 'c', BSZ-2);
        buf[BSZ-2] = '';
        buf[BSZ-1] = 'X';
        fprintf(fp, "hello, world");
        fclose(fp);
        printf("after fclose: %s
", buf);
        printf("len of string in buf = %ld
", (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]#
原文地址:https://www.cnblogs.com/benxintuzi/p/4763919.html