简介
本章主要介绍C所提供的标准I/O操作。不同于前面的open只能在Linux上被支持,C标准接口能够在不同系统中被支持。两个基本的概念,关于底层的I/O操作,效率问题。
- 磁盘,读写最下层就是磁盘的操作。所有的磁盘操作都是基于块进行,这句话的意思就是如果跨越块进行读写会导致效率降低,因此读写请求若选择块大小的整数倍和约数倍的话,就能够保证不会出现跨越块进行读写的问题。
- 内存,常听到的内存对齐问题。CPU进行读写数的时候,对于32位的机器,一次读取写入4字节,并且是按照起始地址开始的,例如CPU要读取一个值时,它只会从4的倍数的地址进行读写,只会读取0,4,8这些地址(起始地址为0)。因此内存对齐的好处是,假设我们要读一个4字节的值,地址在0x5,那么CPU就要先读0x4,取出3字节;然后再读0x8,取出1字节,相当于进行了两次操作,严重影响效率;对齐的话,就能够保证不会产生多余的操作。
而C的接口默认优化了这些问题,内存对齐一般编译器会优化好。
** 其他 **:
分区对齐:将分区的起始位置放在扇区的0处,这样才能保证每一个block都在单独的一个扇区中,一个扇区中包含多个block,若不对齐,那么将会导致一个block跨越两个扇区,严重影响速度。
用户缓冲I/O
用户缓冲I/O的作用类似于内核的缓冲区,在用户空间设置输入输出的缓冲区,目的也是为了提供I/O效率。
标准I/O不同于系统I/O,它使用的不是文件描述符操作,而是文件指针,也就是”流”,最常见的是标准输入输出流,stdin和stdout,对应于文件描述符0和1。
打开文件
返回的是文件指针,也就是 “流”,下面的接口也是最常用:
1 | #include <stdio.h> |
因为文件描述符和流是对应的,因此也可以由文件描述符转化为 流:
1 | #include <stdio.h> |
两者将会关联,通过一个修改就会影响另一个的操作。模式必须匹配。
关闭流
最常用的就是fclose了:
1 | #include <stdio.h> |
关闭所有和当前进程相关的流接口:
1 | #define _GNU_SOURCE |
该函数是Linux特有的。
从流中读取数据
单字节读取
1 | #include <stdio.h> |
从流中读取下一个字符,并将该无符号字符强转为int返回。
将字符放在流中
1 | #include <stdio.h> |
类似于一个栈,后放入先被读出,一个简单的例子能够很好的表示:
1 | #include <stdio.h> |
1 | icepng@ubuntu:~$ ./test |
按行读取
读取一个字符串
1 | #include <stdio.h> |
从流中最多读取size-1个字节,并存入str中。当所有字节读入时,空字符会被存入字符串末尾。当读到EOF或换行符时读入结束,若读到一个换行符,则将”\n”存入。
也就是说最后一个会放入’\0’。例如:
1 | #include <stdio.h> |
1 | icepng@ubuntu:~$ ./test |
可以得知,c[0-3]存入1-4,c[4]存入’\n’,c[5]存入’\0’。
读取二进制文件
1 | #include <stdio.h> |
从流中读入 nr个数据,每个数据size个字节。返回读入元素的个数。
向流中写数据
和读对应的一些接口
写入单个字符
1 | #include <stdio.h> |
写入字符串
1 | #include <stdio.h> |
写入二进制数据
1 | #include <stdio.h> |
定位流
目的是用于指定位置读写
1 | #include <stdio.h> |
类似于系统I/O中的lseek,偏移也是由whence和offset共同决定的。成功返回0,失败返回-1。
fsetpos函数则用于直接定位到指定位置:
1 | #include <stdio.h> |
rewind函数用于重置流到初始位置:
1 | #include <stdio.h> |
ftell用于返回当前流的位置:
1 | #include <stdio.h> |
类似功能的接口为fgetpos,用于获取流的当前位置,并写入pos中:
1 | #include <stdioh.h> |
参考:http://www.runoob.com/cprogramming/c-function-fgetpos.html
清洗一个流
即将用户缓冲区的数据写入到内核之中。
1 | #include <stdio.h> |
错误与文件结束
一些接口在遇到文件结束EOF和发生错误时返回的一样,如fread等。
用于测试是否在流中设置了错误标志的ferror:
1 | #include <stdio.h> |
测试文件结束标志是否设置的feof:
1 | #include <stdio.h> |
为流清空错误和文件结尾标志的clearerr;
1 | #include <stdio.h> |
获得关联的文件描述符
由流获取到文件描述符,和fdopen正好是相反的功能:
1 | #include <stdio.h> |
返回和流关联的文件描述符,失败时返回-1.
控制缓冲
即对用户缓冲区的控制,可以设置为不缓冲、行缓冲、块缓冲
行缓冲:在每次遇到换行符,缓冲区将被提交给内核缓冲区。(标准输出stdout,即屏幕输出,默认是行缓冲)
块缓冲:默认所有和文件相关的流都是块缓冲。块缓冲又称为全缓冲。
1 | #include <stdio.h> |
模式用于设置三种缓冲形式:
_IONBF:无缓冲
_IOLBF:行缓冲
_IOFBF:块缓冲
buf指向一个size大小的缓冲区空间。无缓冲模式不考虑。如果buf为空,缓冲区则有glibc自动分配。
成功时返回0,不成功返回非0.
注意:在流关闭前,缓冲区必须存在,有时候设置buf为局部变量在作用域结束后导致缓冲区销毁;需注意在之前关闭流。
其他
标准C多线程访问,本质上是线程安全的,即多线程访问,默认会加锁处理。也可以人为加锁。