文件和I/O
索引:
- 目录操作
- 打开/关闭文件
- 文件位移量
- read/write
- 复制文件描述符
- fcntl函数
- ioctl函数
- stat系列函数
- UNIX主要的文件类型
- 进程相关ID
- 文件存取许可权位
- access函数
- umask函数
- chmod系列函数
- chown系列函数
- 文件长度
- truncate系列函数
- link系列函数
- symlink/readlink
- 文件时间
- mkdir/rmdir
- chdir系列函数
- st_dev和st_rdev的含义
- sync系列函数
- 标准I/O的缓存方式
- fflush函数
- 标准I/O流的打开/关闭
- 一次读一个字符的标准I/O函数
- 标准I/O函数出错和文件尾判断
- ungetc函数
- 一次写一个字符的标准I/O函数
- 每次输入一行的标准I/O函数
- 每次输出一行的标准I/O函数
- 二进制标准I/O函数
- 定位流的标准I/O函数
- 格式化输出标准I/O
- 格式化输入标准I/O
- 获得一个流的描述符
- 创建临时文件的标准I/O
- 非阻塞I/O
1.目录操作
函数有opendir、readdir、closedir等。
使用的头文件是dirent.h,编译选项为cc … -lc。
原型如下:
DIR *opendir(const char *dirname); 返回:若成功则为指针,若出错则为NULL struct dirent *readdir(DIR *dirp); 返回:若成功则为指针,若在目录尾或出错则为NULL void rewinddir(DIR * dp); int closedir(DIR *dirp); 返回:若成功则为0,若出错则为-1
2.打开/关闭文件
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char * pathname, int oflag,.../*, mode_t mode * / ); 返回:若成功为文件描述符,若出错为-1
打开方式:
- O_RDONLY, 只读打开;
- O_WRONLY, 只写打开;
- O_RDWR, 读写打开;
- O_APPEND, 每次写到加到文件的尾端;
- O_CREAT, 文件不存在则创建;
- O_EXCL, 如果同时指定O_CREAT,而文件已经存在,则出错;
- O_TRUNC, 将文件长度截为0;
- O_NOCTTY, 若打开的是终端设备,则不把该设备作为进程的控制终端;
- O_NONBLOCK,对FIFO、块设备文件或字符设备文件,设置非阻塞方式;
- O_SYNC, 每次write都等到物理完成;
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int creat(const char * pathname, mode_t mode); 返回:若成功为只写打开的文件描述符,若出错为-1
此函数等效于open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
当创建一个文件时,如何保证原子性?-- 创建文件时设置O_CREATE和O_EXCL。
#include <unistd.h> int close(int filedes);
关闭文件。
3.文件位移量
通常读、写操作都从当前文件位移量出开始,并使位移量增加所读或所写的字节数,按系统默认,打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。
#include <sys/types.h> #include <unistd.h> off_t lseek(int filedes, off_t offset, int whence); 返回:若成功为新的文件位移,若出错为-1
对参数offset 的解释与参数whence的值有关:
- 若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处offset个字节;
- 若whence是SEEK_CUR,则将该文件的位移量设置为其当前值加offset, offset可为正或负;
- 若whence是SEEK_END,则将该文件的位移量设置为文件长度加offset, offset可为正或负;
lseek还可以用来确定涉及的文件是否可以设置位移量,如对管道或FIFO调用lseek回出错,并设errno为EPIPE。
文件的位移量可以是负值,所以判断lseek的返回值应该测试它是否等于-1,而不是小于0。
文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞。
4.read/write
#include <unistd.h> ssize_t read(int filedes, void *buff, size_t nbytes); 返回:读到的字节数,若已到文件尾为0,若出错为-1
有多种情况可使实际读到的字节数少于要求读字节数:
- 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件尾端);
- 当从终端设备读时,通常一次最多读一行;
- 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数;
- 某些面向记录的设备,例如磁带,一次最多返回一个记录。
读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。
#include <unistd.h> ssize_t write(int filedes, const void * buff, size_t nbytes); 返回:若成功为已写的字节数,若出错为-1
对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。
使用read和write进行I/O操作,当缓冲区大大小等于文件系统的块大小时,效率最高。
当多个进程同时对某一个文件进行写操作时,如何保证原子性?-- 打开文件写时,设置O_APPEND选项。
5.复制文件描述符
复制文件描述符可以使用dup、dup2和fcntl三种方法。
#include <unistd.h> int dup(int filedes); int dup2(int filedes, int filedes2); 两函数的返回:若成功为新的文件描述符,若出错为-1
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。
用dup2则可以用filedes2参数指定新描述符的数值。如果filedes2已经打开,则先将其关闭。如若filedes等于filedes2,则dup2 返回filedes2,而不关闭它。
6.fcntl函数
#include <sys/types.h> #include <unistd.h> #include <fcntl.h> int fcntl(int filedes, int cmd,.../* int arg * / ); 返回:若成功则依赖于cmd(见下),若出错为-1
fcntl函数有五种功能:
- 复制一个现存的描述符(cmd = F_DUPFD);
- 获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD);
- 获得/设置文件状态标志(cmd = F_GETFL或F_SETFL);
- 获得/设置异步I/O有权(cmd = F_GETOWN或F_SETOWN);
- 获得/设置记录锁(cmd = F_GETLK, F_SETLK或F_SETLKW)。
F_DUPFD
复制文件描述符filedes,新文件描述符作为函数值返回,新描述符与filedes共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志则被清除,这表示该描述符在exec时仍保持开放。
F_GETFD
对应于filedes的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC。
F_SETFD
对于filedes设置文件描述符标志。新标志值按第三个参数(取为整型值)设置。很多现存的涉及文件描述符标志的程序并不使用常数FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)。
F_GETFL
对应于filedes的文件状态标志作为函数值返回。文件状态标志参见open函数。对于O_RDONLY、O_WRONLY、O_RDWR,要使用屏蔽字O_ACCMODE来取得存取方式位才能进行比较。
F_SETFL
将文件状态标志设置为第三个参数的值(取为整型值)。可以更改的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC。
F_GETOWN
取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN
设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg指定一个进程ID,负的arg表示等于arg绝对值的一个进程组ID。
在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
7.ioctl函数
#include <unistd.h> /* SVR4 */ #include <sys/ioctl.h> /* 4.3+BSD * / int ioctl(int filedes, int request, ... ); 返回:若出错则为-1,若成功则为其他值
ioctl主要用于终端I/O和设备控制。
8.stat系列函数
#include <sys/types.h> #include <sys/stat.h> int stat(const char *pathname, struct stat *buf); int fstat(int filedes, struct stat *buf); int lstat(const char *pathname, struct stat *buf); 三个函数的返回:若成功则为0,若出错则为-1
给定一个pathname,stat函数返回一个与此命名文件有关的信息结构;
fstat函数获得已在描述符filedes上打开的文件的有关信息;
lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。
9.UNIX主要的文件类型
文件类型有:普通文件、目录文件、字符特殊文件、块特殊文件、FIFO、套接口(socket)、符号连接。
文件类型信息包含在stat结构的st_mode成员中,使用如下的宏确定文件类型:
- S_ISREG(), 普通文件
- S_ISDIR(), 目录文件
- S_ISCHR(), 字符特殊文件
- S_ISBLK(), 块特殊文件
- S_ISFIFO(),管道或FIFO
- S_ISLNK(), 符号连接(POSIX.1或SVR4无此类型)
- S_ISSOCK(),套接字(POSIX.1或SVR4无此类型)
早期的UNIX没有上述的宏,需将st_mode与屏蔽字S_IFMT逻辑与,然后与S_IFXXX的常数比较,如:
#define S_ISDIR(mode) ( ( (mode) & S_IFMT) == S_IFDIR)
10.进程相关ID
与一个进程相关联的ID有六个或多个:
- 实际用户ID和实际组ID:标识我们是谁,取自口令文件的登录项,通常,在一个登录会话期间这些值并不改变,但是超级用户进程有方法改变它们。
- 有效用户ID,有效组ID以及添加组ID决定了我们的文件访问权。
- 保存的设置-用户-ID和设置-组-ID在执行一个程序时包含了有效用户ID和有效组ID的副本。
设置-用户-ID位及设置-组-ID位都包含在st_mode值中。这两位可用常数S_ISUID和S_ISGID测试。
11.文件存取许可权位
st_mode值也包含了对文件的存取许可权位。每个文件有9个存取许可权位,可将它们分成三类:
- S_IRUSR,用户-读
- S_IWUSR,用户-写
- S_IXUSR,用户-执行
- S_IRGRP,组-读
- S_IWGRP,组-写
- S_IXGRP,组-执行
- S_IROTH,其他-读
- S_IWOTH,其他-写
- S_IXOTH,其他-执行
12.access函数
#include <unistd.h> int access(const char * pathname, int mode); 返回:若成功则为0,若出错则为-1
access函数按实际用户ID和实际组ID进行存取许可权测试,参数mode是下列常数的逐位或运算:
- R_OK,测试读许可权
- W_OK,测试写许可权
- X_OK,测试执行许可权
- F_OK,测试文件是否存在
13.umask函数
umask函数为进程设置文件方式创建屏蔽字,并返回以前的值。(这是少数几个没有出错返回的函数中的一个)
#include <sys/types.h> #include <sys/stat.h> mode_t umask(mode_t cmask); 返回:以前的文件方式创建屏蔽字
其中,参数cmask由S_IRUSR, S_IWUSR等逐位“或”构成。
14.chmod系列函数
#include <sys/types.h> #include <sys/stat.h> int chmod(const char * pathname, mode_t mode); int fchmod(int filedes, mode_t mode); 两个函数返回:若成功则为0,若出错则为-1
为了改变一个文件的许可权位,进程的有效用户ID必须等于文件的所有者,或者该进程必须具有超级用户许可权。
参数mode是下列常数的某种逐位或运算:
- S_ISUID,执行时设置-用户-ID
- S_ISGID,执行时设置-组-ID
- S_ISVTX,保存正文
- S_IRWXU,用户(所有者)读、写和执行
- S_IRUSR,用户(所有者)读
- S_IWUSR,用户(所有者)写
- S_IXUSR,用户(所有者)执行
- S_IRWXG,组读、写和执行
- S_IRGRP,组读
- S_IWGRP,组写
- S_IXGRP,组执行
- S_IRWXO,其他读、写和执行
- S_IROTH,其他读
- S_IWOTH,其他写
- S_IXOTH,其他执行
chmod函数在下列条件下自动清除两个许可权位:
- 如果我们试图设置普通文件的粘住位(S_ISVTX ),而且又没有超级用户优先权,那么mode中的粘住位自动被关闭。
- 新创建文件的组ID可能不是调用进程所属的组。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组ID中的一个,以及进程没有超级用户优先数,那么设置-组-ID位自动被关闭。
粘住位:
如果一个可执行程序文件的S_ISVTX粘住位被设置了,那么在该程序第一次执行并结束时,该程序正文的一个文本被保存在交换区,这使得下次执行该程序时能较快地将其装入内存区。现今较新的UNIX系统大多数都具有虚存系统以及快速文件系统,所以不再需要使用这种技术。
SVR4和4.3+BSD中粘住位的主要针对目录。如果对一个目录设置了粘住位,则只有对该目录具有写许可权的用户并且满足下列条件之一,才能删除或更名该目录下的文件:
- 拥有此文件
- 拥有此目录
- 是超级用户
目录/tmp是设置粘住位的主要地方。
15.chown系列函数
#include <sys/types.h> #include <unistd.h> int chown(const char * pathname, uid_t owner, gid_t group); int fchown(int filedes, uid_t owner, gid_t group); int lchown(const char * pathname, uid_t owner, gid_t group); 三个函数返回:若成功则为0,若出错则为-1
用于更改文件的用户ID和组ID,在符号连接情况下,lchown更改符号连接本身的所有者,而不是该符号连接所指向的文件。
SVR4,4.3+BSD和XPG3允许将参数owner或group指定为-1,以表示不改变相应的ID。
基于伯克利的系统一直规定只有超级用户才能更改一个文件的所有者。系统V则允许任一用户更改他们所拥有的文件的所有者。
按照_POSIX_CHOWN_RESTRICTED的值,POSIX.1在这两种形式的操作中选用一种。
16.文件长度
stat结构的成员st_size包含以字节为单位的该文件的长度,此字段只对普通文件、目录文件和符号连接有意义:
- 对于普通文件,其长度可以为0,在读这种文件时,将得到文件结束指示。
- 对于目录,文件长度通常是一个数,例如16或512的整倍数。
- 对于符号连接,文件长度是在文件名中的实际字节数(不含NULL字符)。
SVR4和4.3+BSD也提供字段st_blksize和st_blocks。第一个是对文件I/O较好的块长度,第二个是所分配的实际512字节块块数。
17.truncate系列函数
#include <sys/types.h> #include <unistd.h> int truncate(const char *pathname, off_t length); int ftruncate(int filedes, off_t length); 两个函数返回;若成功则为0,若出错则为-1
将由路径名pathname或打开文件描述符filedes指定的一个现存文件的长度截短为length。如果该文件以前的长度大于length,则超过length以外的数据就不再能存取。如果以前的长度短于length,则其后果与系统有关。如果某个实现的处理是扩展该文件,则在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是在文件中创建了一个空洞)。
将一个文件的长度截短为0是一个特例,用O_TRUNC标志可以做到这一点。
18.link系列函数
#include <unistd.h> int link(const char *existingpath, const char *newpath); 返回:若成功则为0 ,若出错则为-1
此函数创建一个新目录项newpath,它引用现存文件。如若newpath已经存在,则返回出错。只有超级用户进程可以创建指向一个目录的新连接。
#include <unistd.h> int unlink(const char *pathname); 返回:若成功则为0 ,若出错则为-1
此函数删除目录项,并将由pathname所引用的文件的连接计数减1。如果该文件还有其他连接,则仍可通过其他连接存取该文件的数据。如果出错,则不对该文件作任何更改。只有当连接计数达到0时,该文件的内容才可被删除。另一个条件也阻止删除文件的内容:只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查使该文件打开的进程计数,如果该计数达到0,然后内核检查其连接计数,如果这也是0,那么就删除该文件的内容。
unlink的这种特性经常被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来。
如果pathname是符号连接,那么unlink涉及的是符号连接而不是由该连接所引用的文件。超级用户可以调用带参数pathname的unlink指定一个目录,但是通常不使用这种方式,而使用函数rmdir。
#include <stdio.h> int remove(const char *pathname); 返回:若成功则为0 ,若出错则为-1
对于文件,remove的功能与unlink相同;对于目录,remove的功能与rmdir相同。
#include <stdio.h> int rename(const char *oldname, const char *newname); 返回:若成功则为0,若出错则为-1
文件或目录用rename函数更名。根据oldname是指文件还是目录,有两种情况要加以说明。我们也应说明如果newname已经存在将会发生什么:
- 如果oldname说明一个文件而不是目录,那么为该文件更名。在这种情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname更名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写许可权,因为将更改这两个目录。
- 如若oldname说明一个目录,那么为该目录更名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有. 和..项)。如果newname存在(而且是一个空目录),则先将其删除,然后将oldname更名为newname。另外,当为一个目录更名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr/foo更名为/usr/foo/testdir,因为老名字(/usr/foo)是新名字的路径前缀,因而不能将其删除。
- 作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。
如若newname已经存在,则调用进程需要对其有写许可权(如同删除情况一样)。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行许可权。
19.symlink/readlink
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); 返回:若成功则为0,若出错则为-1
该函数创建了一个指向actualpath的新目录项sympath,在创建此符号连接时,并不要求actualpath已经存在。并且actualpath和sympath并不需要位于同一文件系统中。
#include <unistd.h> int readlink(const char *pathname, char *buf, int bufsize); 返回:若成功则为读的字节数,若出错则为-1
因为open函数跟随符号连接,所以需要有一种方法打开该连接本身,并读该连接中的名字,readlink函数提供了这种功能。此函数组合了open、read和close的所有操作。如果此函数成功,则它返回读入buf的字节数。在buf中返回的符号连接的内容不以null字符终止。
20.文件时间
在stat结构中有三个时间域:
- st_atime:文件数据的最后存取时间,如read;
- st_mtime:文件数据的最后修改时间,如write;
- st_ctime:i节点状态的最后更改时间,如chmod、chown;
一个文件的存取和修改时间可以用utime函数更改:
#include <sys/types.h> #include <utime.h> int utime(const char *pathname, const struct utimbuf *times); 返回:若成功则为0,若出错则为-1
此函数所使用的结构是:
struct utimbuf { time_t actime; /*access time*/ time_t modtime; /*modification time*/ };
此结构中的两个时间值是日历时间。
此函数的操作以及执行它所要求的优先权取决于times参数是否是NULL:
- 如果times是一个空指针,则存取时间和修改时间两者都设置为当前时间。为了执行此操作必须满足下列两条件之一:(a)进程的有效用户ID必须等于该文件的所有者ID;(b)进程对该文件必须具有写许可权。
- 如果times是非空指针,则存取时间和修改时间被设置为times所指向的结构中的值。此时,进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件只具有写许可权是不够的。
注意,我们不能对更改状态时间st_ctime指定一个值,当调用utime函数时,此字段被自动更新。
21.mkdir/rmdir
mkdir创建目录:
#include <sys/types.h> #include <sys/stat.h> int mkdir(const char *pathname, mode_t mode); 返回:若成功则为0,若出错则为-1
用rmdir函数删除一个空目录:
#include <unistd.h> int rmdir(const char *pathname); 返回:若成功则为0,若出错则为-1
如果此调用使目录的连接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在连接计数达到0时,有一个或几个进程打开了此目录,则在此函数返回前删除最后一个连接及.和..项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录(即使某些进程打开该目录,它们在此目录下,也不能执行其他操作,因为为使rmdir函数成功执行,该目录必须是空的)。
22.chdir系列函数
进程调用chdir或fchdir函数可以更改当前工作目录:
#include <unistd.h> int chdir(const char *pathname); int fchdir(int filedes); 两个函数的返回:若成功则为0,若出错则为-1
得到当前工作目录的绝对路径名:
#include <unistd.h> char *getcwd(char *buf, size_t size); 返回:若成功则为buf,若出错则为NULL
向此函数传递两个参数,一个是缓存地址buf,另一个是缓存的长度size。该缓存必须有足够的长度以容纳绝对路径名再加上一个null终止字符,否则返回出错。
23.st_dev和st_rdev的含义
每个文件系统都由其主、次设备号而为人所知。设备号所用的数据类型是基本系统数据类型dev_t。
我们通常可以使用两个大多数实现都定义的宏:major和minor来存取主、次设备号。这就意味着我们无需关心这两个数是如何存放在dev_t对象中的。
系统中每个文件名的st_dev值是文件系统的设备号,该文件系统包含了该文件名和其对应的i节点。
只有字符特殊文件和块特殊文件才有st_rdev值。此值包含该实际设备的设备号。
24.sync系列函数
#include <unistd.h> void sync(void); int fsync(int filedes); 返回:若成功则为0,若出错则为-1
传统的UNIX实现在内核中设有缓冲存储器,大多数磁盘I/O都通过缓存进行。当将数据写到文件上时,通常该数据先由内核复制到缓存中,如果该缓存尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓存以便存放其他磁盘块数据时,再将该缓存排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称之为延迟写。
sync只是将所有修改过的块的缓存排入写队列,然后就返回,它并不等待实际I/O操作结束。
函数fsync只引用单个文件(由文件描述符filedes指定),它等待I/O结束,然后返回。fsync可用于数据库这样的应用程序,它确保修改过的块立即写到磁盘上。比较一下fsync和O_SYNC标志,当调用fsync时,它更新文件的内容,而对于O_SYNC,则每次对文件调用write函数时就更新文件的内容。
25.标准I/O的缓存方式
标准I/O提供了三种类型的缓存:
- 全缓存。在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。对于驻在磁盘上的文件通常是由标准I/O库实施全缓存的。
- 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I/O库执行I/O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。
- 不带缓存。标准I/O库不对字符进行缓存。如果用标准I/O函数写若干字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
使用下列函数更改缓存类型:
#include <stdio.h> void setbuf(FILE *fp, char *buf); int setvbuf(FILE *fp, char *buf, int mode, size_t size); 返回:若成功则为0,若出错则为非0
可以使用setbuf函数打开或关闭缓存机制。为了带缓存进行I/O,参数buf必须指向一个长度为BUFSIZ的缓存(该常数定义在<stdio.h>中)。通常在此之后该流就是全缓存的,但是如果该流与一个终端设备相关,那么某些系统也可将其设置为行缓存的。为了关闭缓存,将buf设置为NULL。
使用setvbuf,我们可以精确地说明所需的缓存类型。这是依靠mode参数实现的:
- _IOFBF,全缓存
- _IOLBF,行缓存
- _IONBF,不带缓存
如果指定一个不带缓存的流,则忽略buf和size参数。如果指定全缓存或行缓存,则buf和size可以可选择地指定一个缓存及其长度。如果该流是带缓存的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓存。适当长度指的是由stat结构中的成员st_blksize所指定的值。如果系统不能为该流决定此值(例如若此流涉及一个设备或一个管道),则分配长度为BUFSIZ的缓存。
要了解,如果在一个函数中分配一个自动变量类的标准I/O缓存,则从该函数返回之前,必须关闭该流。另外,SVR4将缓存的一部分用于它自己的管理操作,所以可以存放在缓存中的实际数据字节数少于size。一般而言,应由系统选择缓存的长度,并自动分配缓存。在这样处理时,标准I/O库在关闭此流时将自动释放此缓存。
26.fflush函数
任何时候,我们都可强制刷新一个流。
#include <stdio.h> int fflush(FILE *fp); 返回:若成功则为0,若出错则为EOF
此函数使该流所有未写的数据都被传递至内核。作为一种特殊情形,如若fp是NULL,则此函数刷新所有输出流。
27.标准I/O流的打开和关闭
下列三个函数可用于打开一个标准I/O流:
#include <stdio.h> FILE *fopen(const char *pathname, const char *type); FILE *freopen(const char *pathname, const char *type, FILE *fp); FILE *fdopen(int filedes, const char *type); 三个函数的返回:若成功则为文件指针,若出错则为NULL
这三个函数的区别是:
- fopen打开路径名由pathname指示的一个文件。
- freopen在一个特定的流上(由fp指示)打开一个指定的文件(其路径名由pathname指示),如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
- fdopen 取一个现存的文件描述符(我们可能从open、dup、dup2、fcntl或pipe函数得到此文件描述符),并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符相结合。
type参数指定对该I/O流的读、写方式,ANSI C规定type参数可以有15种不同的值:
- r或rb: 为读而打开;
- w或wb: 使文件成为0长,或为写而创建;
- a或ab: 添加;为在文件尾写而打开,或为写而创建;
- r+或r+b或rb+:为读和写而打开;
- w+或w+b或wb+:使文件为0长,或为读和写而打开;
- a+或a+b或ab+:为在文件尾读和写而打开或创建;
对于fdopen,type参数的意义则稍有区别。因为该描述符已被打开,所以fdopen为写而打开并不截短该文件。另外,标准I/O添加方式也不能用于创建该文件(因为如若一个描述符引用一个文件,则该文件一定已经存在)。
当以读和写类型打开一文件时(type中+号),具有下列限制:
- 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
- 如果中间没有fseek、fsetpos或rewind,或者一个输出操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
调用fclose关闭一个打开的流:
#include <stdio.h> int fclose(FILE *fp); 返回:若成功则为0,若出错则为EOF
在该文件被关闭之前,刷新缓存中的输出数据。缓存中的输入数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓存,则释放此缓存。当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I/O流都被刷新,所有打开的标准I/O流都被关闭。
28.一次读一个字符的标准I/O函数
#include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); 三个函数的返回:若成功则为下一个字符,若已处文件尾端或出错则为EOF
函数getchar等同于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc则不能实现为宏。在<stdio.h>中的常数EOF被要求是一个负值,其值经常是-1。
注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。
29.标准I/O函数出错和文件尾判断
#include <stdio.h> int ferror(FILE *fp); int feof(FILE *fp); 两个函数返回:若条件为真则为非0(真),否则为0(假) void clearerr(FILE *fp);
在大多数实现的FILE对象中,为每个流保持了两个标志:出错标志、文件结束标志。调用clearerr则清除这两个标志。
30.ungetc函数
#include <stdio.h> int ungetc(int c, FILE *fp); 返回:若成功则为c,若出错则为EOF
送回到流中的字符以后又可从流中读出,但读出字符的顺序与送回的顺序相反。应当了解,虽然ANSI C允许支持任何数量的字符回送的实现,但是它要求任何一种实现都要支持一个字符的回送功能。
回送的字符,不一定必须是上一次读到的字符。EOF不能回送。但是当已经到达文件尾端时,仍可以回送一字符。下次读将返回该字符,再次读则返回EOF。之所以能这样做的原因是一次成功的ungetc调用会清除该流的文件结束指示。
31.一次写一个字符的标准I/O函数
#include <stdio.h> 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则不能实现为宏。
32.每次输入一行的标准I/O函数
#include <stdio.h> char *fgets(char *buf, int n,FILE *fp); char *gets(char *buf); 两个函数返回:若成功则为buf,若已处文件尾端或出错则为NULL
这两个函数都指定了缓存地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
对于fgets,必须指定缓存的长度n。此函数一直读到下一个新行符为止,但是不超过n-1个字符,读入的字符被送入缓存。该缓存以null字符结尾。如若该行,包括最后一个新行符的字符数超过n-1,则只返回一个不完整的行,而且缓存总是以null字符结尾。对fgets的下一次调用会继续读该行。
33.每次输出一行的标准I/O函数
#include <stdio.h> int fputs(const char *str, FILE *fp); int puts(const char *str); 两个函数返回:若成功则为非负值,若出错则为EOF
函数fputs将一个以null符终止的字符串写到指定的流,终止符null不写出。注意,这并不一定是每次输出一行,因为它并不要求在null符之前一定是新行符。
puts将一个以null符终止的字符串写到标准输出,终止符不写出。但是,puts然后又将一个新行符写到标准输出。
34.二进制标准I/O函数
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp); size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp); 两个函数的返回:读或写的对象数
fread和fwrite返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于nobj。在这种情况,应调用ferror或feof以判断究竟是那一种情况。对于写,如果返回值少于所要求的nobj,则出错。
35.定位流的标准I/O函数
#include <stdio.h> long ftell(FILE *fp); 返回:若成功则为当前文件位置指示,若出错则为-1L int fseek(FILE *fp,long offset,int whence); 返回:若成功则为0,若出错则为非0 void rewind(FILE *fp);
对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的。ftell用于二进制文件时,其返回值就是这种字节位置。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种位移量的方式,whence的值与lseek函数的相同:
- SEEK_SET表示从文件的起始位置开始
- SEEK_CUR表示从当前文件位置
- SEEK_END表示从文件的尾端。
ANSI C并不要求一个实现对二进制文件支持SEEK_END规格说明,其原因是某些系统要求二进制文件的长度是某个幻数的整数倍,非实际内容部分则充填为0。但是在UNIX中,对于二进制文件SEEK_END是得到支持的。
对于文本文件,它们的文件当前位置可能不以简单的字节位移量来度量。再一次,这主要也是在非UNIX系统中,它们可能以不同的格式存放文本文件。为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0(表示反绕文件至其起始位置),或是对该文件的ftell所返回的值。使用rewind函数也可将一个流设置到文件的起始位置。
#include <stdio.h> int fgetpos(FILE *fp, fpos_t *pos); int fsetpos(FILE *fp, const fpos_t *pos); 两个函数返回:若成功则为0,若出错则为非0
fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。
36.格式化输出标准I/O
#include <stdio.h> int printf(const char *format, ...); int fprintf(FILE *fp, const char *format, ...); 两个函数返回:若成功则为输出字符数,若输出出错则为负值 int sprintf(char *buf, const char *format, ...); 返回:存入数组的字符数
printf将格式化数据写到标准输出,fprintf写至指定的流,sprintf将格式化的字符送入数组buf中。
sprintf在该数组的尾端自动加一个null字节,但该字节不包括在返回值中。
#include <stdarg.h> #include <stdio.h> int vprintf(const char *format, va_list arg); int vfprintf(FILE *fp, const char *format, va_list arg); 两个函数返回:若成功则为输出字符数,若输出出错则为负值 int vsprintf(char *buf, const char *format, va_list arg); 返回:存入数组的字符数
37.格式化输入标准I/O
#include <stdio.h> int scanf(const char *format, ...); int fscanf(FILE *fp, const char *format, ...); int sscanf(const char *buf, const char *format, ...); 三个函数返回:指定的输入项数,若输入出错,或在任意变换前已至文件尾端则为EOF。
38.获得一个流的描述符
对一个流调用fileno以获得其描述符:
#include <stdio.h> int fileno(FILE *fp); 返回:与该流相关联的文件描述符。
39.创建临时文件的标准I/O
#include <stdio.h> char *tmpnam(char *ptr); 返回:指向一唯一路径名的指针 FILE *tmpfile(void); 返回:若成功则为文件指针,若出错则为NULL
tmpnam产生一个与现在文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX。TMP_MAX定义在<stdio.h>中。若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。下一次再调用tmpnam时,会重写该静态区。如若ptr不是NULL,则认为它指向长度至少是L_ tmpnam个字符的数组。(常数L_tmpnam定义在头文件<stdio.h>中)所产生的路径名存放在该数组中,ptr也作为函数值返回。
注意,tmpnam不应使用在新的代码中,这个函数的缺点包括这样的事实:即临时目录是硬连接到目录p_tmpdir的,而在某些UNIX平台上文件名生成受到方法条件的限制。
tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。注意,UNIX对二进制文件不作特殊区分。
tmpfile函数经常使用的标准UNIX技术是先调用tmpnam产生一个唯一的路径名,然后立即unlink它。
tempnam是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀:
#include <stdio.h> char *tempnam(const char *directory, const char *prefix); 返回:指向一唯一路径名的指针
对于目录有四种不同的选择,并且使用第一个为真的作为目录:
- 如果定义了环境变量TMPDIR,则用其作为目录;
- 如果参数directory非NULL,则用其作为目录;
- 将<stdio.h>中的字符串P_tmpdir用作为目录;
- 将本地目录,通常是/tmp ,用作为目录。
如果prefix非NULL,则它应该是最多包含5个字符的字符串,用其作为文件名的头几个字符。
该函数调用malloc函数分配动态存储区,用其存放所构造的路径名。当不再使用此路径名时就可释放此存储区。
40.非阻塞I/O
对于一个给定的描述符有两种方法对其指定非阻塞I/O:
- 如果是调用open以获得该描述符,则可指定O_NONBLOCK标志;
- 对于已经打开的一个描述符,则可调用fcntl打开O_NONBLOCK标志。
POSIX.1要求,对于一个非阻塞的描述符如果无数据可读,则read返回-1,并且errno被设置为EAGAIN。
早期的系统V版本使用的标志O_NDELAY语义与此不同,在新程序中不应该再使用。
对于BSD指定非阻塞方式出错返回EWOULDBLOCK而不是POSIX.1的EAGAIN,这造成了可移植性问题,必须处理这一问题。