Linux 基本I/O库函数

本文详细介绍了Linux中与I/O相关的库函数,包括fopen、fclose、fgetc/fputc、fgets/fputs、fscanf/fprintf等,以及内存转换函数sscanf/sprintf。通过讨论系统调用与库函数的关系,阐述了何时选择使用系统调用和库函数,指出在不同场景下库函数的效率优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

根据《Unix/Linux系统编程》中关于IO库函数部分内容进行总结
包括系统调用与IO库函数关系,基本IO库函数
fopen
fclose
fgetc getchar getc
fputc putchar putc
fgets gets
fputs puts
fscanf scanf sscanf
fprintf printf sprintf
fread
fwrite

1. Linux中的系统调用与I/O库函数

系统调用是文件操作基础,但是系统调用只支持数据块的读写(BLKSIZE如4KB。然而实际用户可能更希望以适合于应用程序的逻辑单元来读写文件,这时候就可以用IO库函数,方便用户使用且效率更高。

IO库函数是建立在系统调用基础上的

系统调用对应的I/O库函数
open()fopen()
read()fread()
write()fwrite()
lseek()fseek()
close()fclose()

可以从上面的表格中看出I/O库函数是建立在系统调用基础上的,即I/O库函数内部会调用系统调用

系统调用对文件的操作是依靠文件描述符(一个整数)进行操作的,而I/O库函数是依靠文件流指针FILE*进行操作的。

fopen()库函数会发出open()系统调用。失败返回NULL,成功则在堆分配一个FILE结构体,该结构体包含了一个内部缓冲区char fbuf[BLKSIZE],其大小通常与文件系统的数据块BLKSIZE一致。FILE还包含一个整数用于表示文件描述符fd,除此之外还有用于操作缓冲区fbuf的各种指针、计数器和状态变量

当调用fgetc(fp)库函数会尝试从文件流FILE*中获取一个字符如果FILE结构体中的内部缓冲区fbuf为空,则发出read(fd,fbuf,BLKSIZE)系统调用,从文件描述符fd指向的文件中取出BLKSIZE个字节并存放在fbuf中,然后从fbuf中返回一个字符作为fgetc库函数的返回值。因此只要fbuf中有数据,fgetc就可以直接从fbuf中获取字符作为返回值,直到fbuf为空再发出read系统调用获取BLKSIZE个字节。即read系统调用仅用于重新装填fbuf,且系统调用总是以文件系统数据块大小(4KB)进行数据传输。

因此,在这个例子中对于单个字符的读写,系统调用是非常低效的,因为系统调用以数据块作为传输单位。这也解释了为什么在一些情景下I/O库函数的效率比系统调用效率高

2. 什么时候使用系统调用,什么时候使用IO库函数

如上所述,如fgetc这样的IO库函数先将数据从内核复制到FILE结构体内部缓冲区,再从内部缓冲区将数据复制到程序的缓冲区,因此IO库函数传输了两次数据而系统调用直接将数据从内核复制到程序的缓冲区,只传输了一次数据。因此对于以数据块BLKSIZE为单位的读写数据来说,系统调用的效率要更高,因为只用传输一次数据而不是两次。

因此对于以BLKSIZE为单位的数据读写,使用系统调用的效率更高。但是如果不是以BLKSIZE为单位进行数据读写(如上文以单个字节进行读写),那么使用IO库函数效率要更高,虽然IO库函数内部也会发出系统调用,但是这些系统调用仅仅是为了填充或者清除内部缓冲区,并不是每一次IO库函数都会发出系统调用的。并且进程进入内核模式(如发出系统调用使之从用户态陷入内核态)的代价要比停留在用户模式更高。

3. 基本I/O库函数

3.1 fopen打开文件

打开文件,成功返回FILE*指针,失败返回NULL并制定errno值来表示错误。

FILE * fopen(const char * filename, const char * modes)

发出open系统调用,并在堆区创建FILE结构体。该结构体对象包含打开文件的文件描述符fd,内部缓冲区fbuf和操作内部缓冲区的各种指针、计数器和状态变量

  • 第一个参数filename:文件路径

  • 第二个参数modes:打开文件方式

    参数值描述
    “r”只读打开文件。读写指针被定位于文件的开始。
    “r+”读写打开文件。读写指针被定位于文件的开始。
    “w”只写打开文件。将文件长度截断为零,读写指针被定位于文件开始
    “w+”读写打开文件。如果文件不存在就创建,否则将截断它。读写指针被定位于文件的开始。
    “a”追加打开文件。如果文件不存在就创建,读写指针被定位于文件的末尾。
    “a+”追加打开文件。如果文件不存在就创建,读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。

3.2 fclose关闭文件

关闭文件流。成功返回 0,否则返回 EOF(-1) 并设置全局变量 errno 来表示错误。

int fclose(FILE * stream)

如果该文件流以写的方式打开,fclose函数会把内部缓冲区fbuf内最后剩余的数据写入到内核缓冲区(把还没写入的数据写入文件)。fclose会发出close系统调用来关闭FILE结构体中的文件描述符,然后释放FILE结构体并将FILE*重置为NULL

3.3 fgetc读取单个字符

从文件流中读一个字符,该字符的ASCII值作为函数的返回值。若返回值为EOF(-1),说明文件结束或错误。

int fgetc(FILE *stream)

注意fgetc返回的是整数,因为如果读取到文件末尾或者错误时返回EOF(-1)因此返回值不能是无符号类型。

对于stdin,使用getchar函数从标准输入流stdin中读取一个字符,相当于fgetc(stdin)

int getchar()

fgetc和getc的区别

fgetc和getc都可以从指定的文件流中读取下一个字符。但是fgetc一定是I/O库函数,而getc可以被实现为宏

#define getc(_fp) _IO_getc (_fp)

3.4 fputc写入单个字符

将字符c写入文件流中读写指针指向位置。如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF。

int fputc(int c, FILE *stream)

对于stdout,使用putchar函数向标准输出流stdout中写入一个字符,相当于fputc(c,stdout)

int putchar(int c)

fputc和putc的区别

fputc和putc都可以向指定的文件流中写入一个字符。但是fputc一定是I/O库函数,而putc可以被实现为宏

#define putc(_ch, _fp) _IO_putc (_ch, _fp)

3.5 fgets读取多个字符

从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取到 (n-1) 个字符、或者读取到换行符、到达文件末尾时,停止读取。

char * fgets(char * str, int n, FILE * stream)
  • 参数str:缓冲区,fgets函数将读取到的数据保存在该缓冲区中
  • 参数n:最大读取字符数(包括最后的’\0’),通常传递的是str字符数组长度
  • 参数stream:文件流指针

如果成功,该函数返回相同的 str 参数。如果到达文件末尾、没有读取到任何字符或者发生错误,返回一个空指针。

对于stdin,使用gets函数从标准输入流stdin中读取一行,并把它存储在 str 所指向的字符串中

char *gets(char *str)

3.6 fputs写入多个字符

把字符串写入到指定的流 stream 中,但不包括’\0’。

int fputs(const char * str, FILE * stream)
  • 参数str :字符数组,包含了要写入的以空字符终止的字符序列
  • 参数stream : 文件流

对于stdout,使用puts函数把一个字符串写入到标准输出流stdout,直到’\0’,但不包括’\0’。换行符会被追加到输出中。

int puts(const char *str)

3.7 fscanf格式化输入

从文件流中以格式format读取数据

int fscanf(FILE * stream, const char * format, ...)

对于stdin,使用scanf函数从标准输入流按照格式获取数据

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

3.8 fprintf格式化输出

向文件流以格式format写入数据

int fprintf(FILE * stream, const char * format, ...)

对于stdout,使用printf函数向标准输出流按照格式写入数据

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

3.9 内存中的转换函数

注意这里的sscanf()和sprintf()函数不是I/O库函数,仅仅是内存中的数据转换函数。例如atoi()函数可以将字符串转换为int类型数字,相反没有itoA()函数,因为可以通过sprintf函数完成

3.9.1 sscanf函数

从字符串格式化读取输入

int sscanf(const char * s, const char * format, ...)
3.9.2 sprintf函数

将数据格式化写入到 str 所指向的字符串。

int sprintf(char * str, const char * format, ...)

3.10 fread函数

在第一次调用fread函数时,FILE中的内部缓冲区fbuf是空的,fread发出read(fd,fbuf,BLKSIZE)系统调用填充缓冲区,并设置fbuf的相关指针,计数器和状态变量,以表明fbuf中的数据情况。然后将数据从fbuf中复制到程序(调用处)的缓冲区中以尝试满足fread函数需求。如果FILE内部缓冲区的数据不够,就再发出一个read系统调用填充fbuf,直到满足fread函数或者到达文件末尾则停止

即每次fread函数调用都会从FILE结构体的fbuf中复制数据,当缓冲区为空时才发出read系统调用重新填充fbuf。因此fread()一方面函数一方面接收来自用户程序的调用,另一方面根据fbuf情况向操作系统内核发出read系统调用。

fread()函数:从给定输入流stream读取数据到 ptr 所指向的数组中。成功返回所读的元素个数(不是字节数),失败如遇到文件结束或出错时可能返回0。

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size – 这是要读取的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 文件流。

例子:

	char str[10] = {};
	fread(str, 1, 5, stdin);//从标准输入流读取数据并保存到字符数组

3.11 fwrite函数

初始时FILE结构体内fbuf是空的,fwrite函数将数据写入FILE的fbuf中并调整相关指针、计数器和状态变量。当fbuf满时发出write系统调用将整个缓冲区写入操作系统内核。(这也可以解释当以写方式打开文件时,fclose函数会在关闭文件前将缓冲区内的数据写入到内核缓冲区,因为I/O库函数并不保证当即将数据写入文件)

fwrite()函数:向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
  • ptr – 这是指向要被写入的元素数组的指针。
  • size – 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 文件流

例子:

	char str[] = "这是个测试";
	fwrite(str, sizeof(str), 1, stdout);//将字符数组打印到标准输出流
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值