APUE笔记……标准IO库

时间:2022-10-06 10:03:54
 
       With the standard I/O library, the discussion centers around streams. When we open or create a file with the standard I/O library, we say that we have associated a stream with the file.
       而对于标准 I / O 库,它们的操作则是围绕流( s t r e a m )进行的。当用标准 I / O 库打开或创建一个文件时,我们已使一个流与一个文件相结合。
       当打开一个流时,标准 I / O 函数 f o p e n 返回一个指向 F I L E 对象的指针。该对象通常是一个结构,它包含了 I / O 库为管理该流所需要的所有信息:用于实际 I / O 的文件描述符,指向流缓存的 指针,缓存的长度,当前在缓存中的字符数,出错标志等等。
       When we open a stream, the standard I/O function fopen returns a pointer to a FILE object. This object is normally a structure that contains all the information required by the standard I/O library to manage the stream: the file descriptor used for actual I/O, a pointer to a buffer for the stream, the size of the buffer, a count of the number of characters currently in the buffer, an error flag, and the like.
        
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
Returns: positive if stream is wide-oriented,
negative if stream is byte-oriented,
or 0 if stream has no orientation
       对一个进程预定义了三个流,它们自动地可为进程使用:标准输入、标准输出和标准出错。这三个标准I / O流通过预定义文件指针s t d i n , s t d o u t和s t d e r r加以引用。这三个文件指针同样定义在头文件< s t d i o . h >中。
       标准I / O提供缓存的目的是尽可能减少使用r e a d和w r i t e调用的数量
        
       标准I / O提供了三种类型的缓存:
1)全缓存。在这种情况下,当填满标准I / O缓存后才进行实际I / O操作。对于驻在磁盘上的文件通常是由标准I / O库实施全缓存的。在一个流上执行第一次I / O操作时,相关标准I / O函数通常调用m a l l o c获得需使用的缓存。
术语刷新( f l u s h)说明标准I / O缓存的写操作。缓存可由标准I / O例程自动地刷新(例如当填满一个缓存时),或者可以调用函数ff l u s h刷新一个流。值得引起注意的是在U N I X环境中,刷新有两种意思。在标准I / O库方面,刷新意味着将缓存中的内容写到磁盘上(该缓存可以只是局部填写的)。在终端驱动程序方面(例如在第11章中所述的t c f l u s h函数),刷新表示丢弃已存在缓存中的数据。
(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I / O库执行I / O操作。这允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I / O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。
(3) 不带缓存。标准I / O库不对字符进行缓存。如果用标准I / O函数写若干字符到不带缓存的流中,则相当于用w r i t e系统调用函数将这些字符写至相关联的打开文件上。标准出错流s t d e r r通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
 
 ANSI C要求下列缓存特征:
(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
(2) 标准出错决不会是全缓存的。
 S V R 4和4 . 3 + B S D的系统默认使用下列类型的缓存:
• 标准出错是不带缓存的。
• 如若是涉及终端设备的其他流,则它们是行缓存的;否则是全缓存的。
       With setbuf, we can turn buffering on or off. To enable buffering, buf must point to a buffer of length BUFSIZ, a constant defined in <stdio.h>. Normally, the stream is then fully buffered, but some systems may set line buffering if the stream is associated with a terminal device. To disable buffering, we set buf to NULL.
       With setvbuf, we specify exactly which type of buffering we want. This is done with the mode argument:
_IOFBF
fully buffered
_IOLBF
line buffered
_IONBF
unbuffered
如果指定一个不带缓存的流,则忽略 buf size 参数。如果指定全缓存或行缓存,则 buf s i z e可以可选择地指定一个缓存及其长度。如果该流是带缓存的,而 buf 是N U L L,则标准I / O库将自动地为该流分配适当长度的缓存。适当长度指的是由s t r u c t结构中的成员s t _ b l k s i z e所指定的值。如果系统不能为该流决定此值(例如若此流涉及一个设备或一个管道),则分配长度为B U F S I Z的缓存。
Figure 5.1. Summary of the setbuf and setvbuf functions
Function
mode
buf
Buffer and length
Type of buffering
setbuf
 
non-null
user buf of length BUFSIZ
fully buffered or line buffered
NULL
(no buffer)
unbuffered
setvbuf
_IOLBF
non-null
user buf of length size
fully buffered
NULL
system buffer of appropriate length
_IOFBF
non-null
user buf of length size
line buffered
NULL
system buffer of appropriate length
_IONBF
(ignored)
(no buffer)
unbuffered
 
 一般而言,应由系统选择缓存的长度,并自动分配缓存。在这样处理时,标准I / O库在关闭此流时将自动释放此缓存。
 At any time, we can force a stream to be flushed.
#include <stdio.h>
 
int fflush(FILE *fp);
 
Returns: 0 if OK, EOF on error
This function causes any unwritten data for the stream to be passed to the kernel. As a special case, if fp is NULL, this function causes all output streams to be flushed.
 The following three functions open a standard I/O stream.
#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 filedes, const char *type);
 
All three return: file pointer if OK, NULL on error
 (1) fopen打开路径名由 pathname 指示的一个文件。
(2) freopen在一个特定的流上(由 f p指示)打开一个指定的文件(其路径名由 pathname 指示),如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
(3) fdopen取一个现存的文件描述符(我们可能从o p e n , d u p , d u p 2 , f c n t l或p i p e函数得到此文件描述符),并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的插述符。因为这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用f d o p e n使一个标准I / O流与该描述符相结合。
 
 f o p e n和f r e o p e n是ANSI C的所属部分。而ANSI C并不涉及文件描述符,所以仅有P O S I X . 1具有f d o p e n。
 当以读和写类型打开一文件时( t y p e中+号),具有下列限制:
• 如果中间没有ff l u s h、f s e e k、f s e t p o s或r e w i n d,则在输出的后面不能直接跟随输入。
• 如果中间没有f s e e k、f s e t p o s或r e w i n d ,或者一个输出操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
 注意,在指定w或a类型创建一个新文件时,我们无法说明该文件的存取许可权位(第3章中所述的o p e n函数和c r e a t函数则能做到这一点)。
 
 Three functions allow us to read one character at a time.
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
All three return: next character if OK, EOF on end of file or error
 
    当一个进程正常终止时(直接调用e x i t函数,或从m a i n函数返回),则所有带未写缓存数据的标准I / O流都被刷新,所有打开的标准I / O流都被关闭。
 函数g e t c h a r等同于g e t c ( s t d i n )。前两个函数的区别是g e t c可被实现为宏,而f g e t c则不能实现为宏。这意味着:
(1) getc的参数不应当是具有副作用的表达式。
(2) 因为f g e t c一定是个函数,所以可以得到其地址。这就允许将f g e t c的地址作为一个参数传送给另一个函数。
(3) 调用f g e t c所需时间很可能长于调用g e t c ,因为调用函数通常所需的时间长于调用宏。检验一下< s t d i o . h >头文件的大多数实现,从中可见g e t c是一个宏,其编码具有较高的工作效率。
    在< s t d i o . h >中的常数E O F被要求是一个负值,其值经常是- 1。这就意味着不能将这三个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常数E O F相比较。
 在大多数实现的F I L E对象中,为每个流保持了两个标志:
• 出错标志。
• 文件结束标志。
调用c l e a r e r r则清除这两个标志。
※ After reading from a stream, we can push back characters by calling ungetc.
#include <stdio.h>
int ungetc(int c, FILE *fp);
Returns: c if OK, EOF on error
    当正在读一个输入流,并进行某种形式的分字或分记号操作时,会经常用到回送字符操作。有时需要先看一看下一个字符,以决定如何处理当前字符。然后就需要方便地将刚查看的字符送回,以便下一次调用g e t c时返回该字符。如果标准I / O库不提供回送能力,就需将该字符存放到一个我们自己的变量中,并设置一个标志以便判别在下一次需要一个字符时是调用g e t c,还是从我们自己的变量中取用。
※ Line-at-a-time input is provided by the following two functions.
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
Both return: buf if OK, NULL on end of file or error
这两个函数都指定了缓存地址,读入的行将送入其中。g e t s从标准输入读,而f g e t s则从指定的流读。
 对于f g e t s,必须指定缓存的长度 n。此函数一直读到下一个新行符为止,但是不超过 n-1个字符,读入的字符被送入缓存。该缓存以n u l l字符结尾。如若该行,包括最后一个新行符的字符数超过 n-1,则只返回一个不完整的行,而且缓存总是以n u l l字符结尾。对f g e t s的下一次调用会继续读该行。
 g e t s是一个不推荐使用的函数。问题是调用者在使用g e t s时不能指定缓存的长度。这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果。
※  Line-at-a-time output is provided by fputs and puts.
#include <stdio.h>
 
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
Both return: non-negative value if OK, EOF on error
函数f p u t s将一个以n u l l符终止的字符串写到指定的流,终止符n u l l不写出。注意,这并不一定是每次输出一行,因为它并不要求在n u l l符之前一定是新行符。通常,在n u l l符之前是一个新行符,但并不要求总是如此。p u t s将一个以n u l l符终止的字符串写到标准输出,终止符不写出。但是, p u t s然后又将一个新行符写到标准输出。
    如果总是使用f g e t s和fputs, 那么就会熟知在每行终止处我们必须自己加一个新行符。
       #include "apue.h"
int
main(void)
{
    char    buf[MAXLINE];
 
    while (fgets(buf, MAXLINE, stdin) != NULL)
        if (fputs(buf, stdout) == EOF)
            err_sys("output error");
 
    if (ferror(stdin))
        err_sys("input error");
 
    exit(0);
}
    f p u t s在遇到n u l l字节时就停止,而在结构中可能含有n u l l字节
   
#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);
Both return: number of objects read or written
 
 
   Read or write a binary array. For example, to write elements 2 through 5 of a floating-point array, we could write
    float data[10];
    if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
        err_sys("fwrite error");
Read or write a structure. For example, we could write
    struct {
      short   count;
      long    total;
      char    name[NAMESIZE];
    } item;
 
    if (fwrite(&item, sizeof(item), 1, fp) != 1)
        err_sys("fwrite error");
    在不同系统之间交换二进制数据的实际解决方法是使用较高层次的协议。关于网络协议使用的交换二进制数据的某些技术
 
   
#include <stdio.h>
 
long ftell(FILE *fp);
 
Returns: current file position indicator if OK, 1L on error
int fseek(FILE *fp, long offset, int whence);
 
Returns: 0 if OK, nonzero on error
void rewind(FILE *fp);
    对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的。f t e l l用于二进制文件时,其返回值就是这种字节位置。
    对于文本文件,它们的文件当前位置可能不以简单的字节位移量来度量。为了定位一个文本文件, w h e n c e一定要是S E E K _ S E T,而且 o f f s e t只能有两种值: 0(表示反绕文件至其起始位置),或是对该文件的f t e l l所返回的值。使用r e w i n d函数也可将一个流设置到文件的起始位置。
 
#include <stdio.h>
 
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
 
int fsetpos(FILE *fp, const fpos_t *pos);
Both return: 0 if OK, nonzero on error
The fgetpos function stores the current value of the file's position indicator in the object pointed to by pos. This value can be used in a later call to fsetpos to reposition the stream to that location. f g e t p o s将文件位置指示器的当前值存入由 p o s指向的对象中。在以后调用f s e t p o s时,可以使用此值将流重新定位至该位置。
※  Formatted output is handled by the four printf functions.
#include <stdio.h>
 
int printf(const char *restrict format, ...);
 
int fprintf(FILE *restrict fp, const char *restrict format, ...);
Both return: number of characters output if OK, negative value if output error
int sprintf(char *restrict buf, const char *restrict format, ...);
 
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
Both return: number of characters stored in array if OK, negative value if encoding error
The printf function writes to the standard output, fprintf writes to the specified stream, and sprintf places the formatted characters in the array buf. The sprintf function automatically appends a null byte at the end of the array, but this null byte is not included in the return value.
   
#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,
           ...);
All three return: number of input items assigned,
EOF if input error or end of file before any conversion
 
    在U N I X中,标准I / O库最终都要调用第3章中说明的I / O例程。每个I / O流都有一个与其相关联的文件描述符
※  Note that fileno is not part of the ISO C standard, but an extension supported by POSIX.1.
#include <stdio.h>
 
int fileno(FILE *fp);
 
Returns: the file descriptor associated with the stream
We need this function if we want to call the dup or fcntl functions, for example.
 标准出错如它所应该的那样是非缓存的,而普通文件按系统默认是全缓存的。
※ The ISO C standard defines two functions that are provided by the standard I/O library to assist in creating temporary files.
#include <stdio.h>
 
char *tmpnam(char *ptr);
Returns: pointer to unique pathname
FILE *tmpfile(void);
Returns: file pointer if OK, NULL on error
#include <stdio.h>
 
char *tempnam(const char *directory, const char *prefix);
Returns: pointer to unique pathname
    在标准I / O库中,一个效率不高的不足之处是需要复制的数据量。当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I / O缓存之间(当调用read和write时),第二次是在标准I / O缓存和用户程序中的行缓存之间