30 读书笔记:第5章 标准I/O库 (4)

5.12 实现细节

        每个标准I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数以获得其描述符。

       #include <stdio.h>
       int fileno(FILE *stream);
       // 返回值:与该流相关联的文件描述符

        《UNIX环境高级编程》P125:程序清单5-3 对各个标准I/O流打印缓冲状态信息(有改动)

#include <stdio.h>
#include <stdlib.h>

void pr_stdio(const char *name, FILE *fp);

int main(int argc, char *argv[])
{
    FILE    *fp;
    
    fputs("enter any character\n", stdout);
    if (getchar() == EOF) 
        fprintf(stderr, "getchar error\n");
    fputs("one line to standard error\n", stderr);

    pr_stdio("stdin", stdin);
    pr_stdio("stdout", stdout);
    pr_stdio("stderr", stderr);

    if ((fp = fopen("/etc/modules", "r")) == NULL)
        fprintf(stderr, "fopen error\n");
    if (getc(fp) == EOF)
        fprintf(stderr, "getc error\n");
    pr_stdio("/etc/modules", fp);

    exit(0);
}

void pr_stdio(const char *name, FILE *fp)
{
    printf("stream = %s, ", name);

    if (fp->_IO_file_flags & _IO_UNBUFFERED)
        printf("unbuffered");
    else if (fp->_IO_file_flags & _IO_LINE_BUF)
        printf("line buffered");
    else
        printf("fully buffered");
    printf(", buffer size = %ld\n", fp->_IO_buf_end - fp->_IO_buf_base);
}

        注意,在打印缓冲状态信息之前,先对每个流执行I/O操作,第一个I/O操作通常会为该流分配缓冲区。结构成员_IO_file_flags、_IO_buf_base、_IO_buf_end和常量_IO_UNBUFFERED、_IO_LINE_BUFFERED是由Linux中的GNU标准I/O库定义的。

        运行程序:

$ ./03          stdin、stdout和stderr都连接至终端
enter any character
                    键入换行符
one line to standard error
stream = stdin, line buffered, buffer size = 1024
stream = stdout, line buffered, buffer size = 1024
stream = stderr, unbuffered, buffer size = 1
stream = /etc/modules, fully buffered, buffer size = 4096

$ ./03 < /etc/terminfo/README > std.out 2> std.err          三个流都重定向,再次运行该程序
$ cat std.err 
one line to standard error
$ cat std.out 
enter any character
stream = stdin, fully buffered, buffer size = 4096
stream = stdout, fully buffered, buffer size = 4096
stream = stderr, unbuffered, buffer size = 1
stream = /etc/modules, fully buffered, buffer size = 4096

        从中可见,按系统默认情况是:当标准输入、输出连接至终端时,它们是行缓冲的。行缓冲的长度是1024。注意,这并没有将输入、输出的行长度限制为1024,这只是缓冲区的长度。如果要将2048字节的行写到标准输出,则要进行两次write系统调用。当将这两个流重定向到普通文件时,它们就变成全缓冲的,其缓冲区长度是该文件系统优先选用的I/O长度。从中也可以看出,标准出错如它所应该的那样是非缓冲的,而普通文件按系统默认是全缓冲的。

5.13 临时文件

        ISO C标准I/O库提供了两个函数以帮助创建临时文件。

       #include <stdio.h>
       
       char *tmpnam(char *s);
       // 返回值:指向唯一路径名的指针
       FILE *tmpfile(void);
       // 返回值:若成功则返回文件指针,若出错则返回NULL

        tmpnam函数产生一个与现有文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX。

        若s是NULL,则产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。下次再调用tmpnam时,会重写该静态区。如果s不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组,所产生的路径名存放在该数组中,s也作为函数返回值。

        tmpfile创建一个临时二进制文件,在关闭该文件或程序结束时将自动删除这种文件。

        《UNIX环境高级编程》P127:程序清单5-4 tmpnam和tmpfile函数实例(有改动)

#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1024

int main(int argc, char *argv[])
{
    char    name[L_tmpnam], line[MAXLINE];
    FILE    *fp;

    printf("%s\n", tmpnam(NULL));                   // 路径名在静态区中

    tmpnam(name);                                   // 路径名在name中
    printf("%s\n", name);

    if ((fp = tmpfile()) == NULL) {                 // 创建一个临时二进制文件(类型wb+)
        fprintf(stderr, "tmpfile error\n");
    }   
    fputs("one line of output\n", fp);              // 向临时文件写入数据
    rewind(fp);
    if (fgets(line, sizeof(line), fp) == NULL) {    // 从临时文件中读取数据
        fprintf(stderr,  "fgets error\n");
    }   

    fputs(line, stdout);                            // 将数据输出到标准输出

    exit(0);
}

        执行程序:

$ ./04 
/tmp/fileQCvvmy
/tmp/filesRJBoU
one line of output

        tmpfile函数经常使用的标准UNIX技术是先调用tmpnam产生一个唯一的路径名,然后,用该路径名创建一个文件,并立即ulink它。

        Single UNIX Specification为处理临时文件定义了另外两个函数:tempnam、mkstemp。

       #include <stdio.h>

       char *tempnam(const char *dir, const char *pfx);
       // 返回值:指向唯一路径名的指针

        tempnam是tmpnam的一个变体,它允许调用者为所产生的路径指定目录和前缀。对于目录有4种不同的选择,按下列顺序判断其条件是否为真,并使用第一个为真的作为目录:

                (1) 如果定义了环境变量TMPDIR,则用其作为目录。

                (2) 如果参数dir非NULL,则用其作为目录。

                (3) 将<stdio.h>中的字符串P_tmpdir用作目录。

                (4) 将本地目录(通常是/tmp)用作目录。

        如果pfx非NULL,则它应该是最多包含5个字符的字符串,用其作为文件名的头几个字符。

        该函数调用malloc函数分配动态存储区,用其存放所构造的路径名。当不再使用此路径名时就可释放此存储区。

        《UNIX环境高级编程》P128:程序清单5-5 演示tempnam函数

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "usage: a.out <directory> <prefix>\n");
        exit(-1);
    }   

    printf("%s\n", tempnam(argv[1][0] != ' ' ? argv[1] : NULL,
                argv[2][0] != ' ' ? argv[2] : NULL));

    exit(0);
}

        如果命令行参数中目录或前缀任意一个以空白开始,则将其作为null指针传送给该函数。

$ ./05 /home/user TEMP                      指定目录和前缀
/home/user/TEMP8lAyTL
$ ./05 " " PFX                              使用默认目录:P_tmpdir
/tmp/PFXc39a1p
$ TMPDIR=/var/tmp ./05 /tmp " "             使用环境变量;无前缀
/var/tmp/file66E43J                         环境变量覆盖目录
$ TMPDIR=/no/such/dir ./05 /home/user/ QQQ
/home/user/QQQzS4UeD                        忽略无效环境目录

        mkstemp类似于tmpfile,但是该函数返回的不是文件指针,而是临时文件的打开文件描述符。

       #include <stdlib.h>

       int mkstemp(char *template);
        // 返回值:若成功则返回文件描述符,若出错则返回-1

        它所返回的文件描述符可用于读、写该文件。临时文件的名字是用template字符串参数选择的。该字符串是一个路径名,其最后6个字符设置为XXXXXX。该函数用不同字符代换XXXXXX,以创建唯一路径名。若mkstemp成功返回,它就会修改template字符串以反映临时文件的名字。

        mkstemp创建的临时文件不会自动删除。

        使用tmpnam和tempnam的一个不足之处是:在返回唯一路径名和应用程序用该路径名创建文件之间有一个时间窗口。在该时间窗口期间,另一个进程可能创建一个同名文件。tmpfile和mkstemp函数则不会产生此种问题。

5.14 标准I/O的替代软件

        标准I/O库并不完善,其中,有些属于基本设计,而大多数则与各种不同的实现有关。

        标准I/O库的一个不足之处是效率不高,这与它需要复制的数据量有关。当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I/O缓冲之间,第二次是在标准I/O缓冲区和用户程序中的行缓冲区之间。快速I/O库避免了这一点,其方法是使读一行的函数返回指向改行的指针,而不是将该行复制到另一个缓冲区中。

        sfilo,速度上与fio相近,通常快于标准I/O库。

        映射文件mmap函数。

        许多标准I/O库实现可用于C函数库中,这种C函数库是为内存较小的系统设计的。这些实现对于合理内存要求的关注超过对可移植性、速度以及功能等方面的关注。如:uClibc C库和newlibc C库。

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)