美文网首页Linux学习|Gentoo/Arch/FreeBSDLinuxLinux学习之路
APUE读书笔记-05标准输入输出库(1)

APUE读书笔记-05标准输入输出库(1)

作者: QuietHeart | 来源:发表于2020-04-18 22:03 被阅读0次

    1、简介

    在本章,我们描述了标准输入输出库。由于这个库也被许多非UNIX的操作系统实现了,所以这个库由 ISO C 标准指定。其它的接口被 SUS 定义,作为 ISO C 标准的扩展。

    标准输入输出库处理了诸如缓存分配,以及分配优化的大小的块进行 I/O ,这样我们就不必担心如何使用正确的块大小进行 I/O 了(如原书3章9节所描述的那样)。这样,这个库就很容易使用了,但是同时,也引入了我们没有预料到的其他一些问题。

    标准 I/O 库由Dennis Ritchie在大约1975年编写,它作为Mike Lesk编写的可移植 I/O 库的主要版本.并且在30年的期间内,对它改动非常少。

    译者注

    原文参考

    参考: APUE2/ch05lev1sec1.html

    2、流和文件描述符号

    我们使用系统调用来操作文件的时候,是通过文件描述符号来标识这个文件,如果我们使用标准库函数来进行文件操作的我们通常是把一个 stream 和这个文件相关联了,然后通过这个 stream 对这个流进行操作,也即,标准 I/O 库是以流( stream )为中心进行操作的。

    当我们使用库函数 fopen 打开一个文件的时候,会返回一个指向 FILE 对象的指针,这个对象包含了 I/O 库管理 stream 需要的所有信息,例如被操作的文件的描述符号,指向这个 stream 的缓存的指针,以及缓存的大小(注意这个缓存和系统调用时可能经过的内核的缓存不同,经过内核的缓存更接近下层,在第3章8节讨论 I/O 效率时的最后也提到过,但这属于内核驱动范围了,可以暂时忽略),当前缓存中的字符数目,错误标志等等。我们把这个 FILE 对象做为参数传递给标准输入输出库函数,就可以实现相应的操作了(使用库函数操作,也不用费心去关心输入输出缓存设置为多大才好了)。

    应用程序不用检验 FILE 对象。引用一个流,只需将 FILE 指针作为参数传递给每个标准 I/O 函数。本书中,我们称指向 FILE 的指针(类型为 FILE* )为文件指针。

    通常,有单字节和多字节字符。 streamorientation 用于决定读写的是单字节字符还是多字节字符。在最开始一个 stream 创建的时候,它是没有 orientation 的,这时候,如果使用了一个多字节 I/O 函数对这个 stream 进行操作(例如 <wchar.h> 中的函数),那么这个 stream 就被设置为 wide-oriented 的;同理如果用单字节的 I/O 函数对这个 stream 进行操作,那么这个 stream 就被设置为 byte-oriented 的。一旦设置了 stream 的方向,除非被关闭,否则就不能改变了。当一个 streamset 的时候,只有两个函数可以改变这个 streamorientation 。它们是: fwidefreopen .

    freopen 原型如下:

    FILE *freopen(const char *path, const char *mode, FILE *stream);
    

    它会把 streamorientationclear .把原来的 stream 给关掉,然后把参数指定的新的 stream 和这个文件相互关联。

    fwide 原型如下:

    #include <stdio.h>
    #include <wchar.h>
    int fwide(FILE *fp, int mode);
    

    这里,如果 mode 是负数,那么设置成 byte-oriented ;如 mode 是正数,那么设置成 wide-oriented ;如果 mode 是0那么不会做设置,但也返回当前流的 orientation 值。

    需要注意的是 fwide 不会改变已经被 orientedstream ,也不会返回 error (也就是那个流刚开始被创建之后没有被使用过就没有被设置过,这个时候则可以用这个函数来设置)。

    译者注

    原文参考

    参考: APUE2/ch05lev1sec2.html

    3、标准输入、标准输出和标准错误

    有三个重要的流: stdin , stdout , stderr 表示标准输入,输出和错误流,它们对应的文件描述符号是: STDIN_FILENO , STDOUT_FILENO ,和 STDERR_FILENO .

    译者注

    原文参考

    参考: APUE2/ch05lev1sec3.html

    4、关于库函数和 buffer

    使用标准输入输出( Standard I/O )库函数进行操作的目的就是为了使用最小数目的的 readwrite 系统调用(通过自动管理设置多大的缓存等等)。 Standard I/O 一般选择合适的大小来分配缓存。可能是 BUFSIZ 常量的值(在 <stdio.h> 中定义),也可能是 stat 结构中的 st_blksize 成员。

    (1)三种类型的缓存

    • Fully Buffered .在这种情况下当 stand i/o 的缓存被填满的时候才会发生 i/o .一般使用 standard i/o 操作磁盘上面的文件的时候使用的是这种类型的 buffer .

      这里需要注意在UNIX环境中,刷新( flush )这个术语有两种意思。在标准 I/O 库方面,刷新意味着将缓存中的内容写到磁盘上(该缓存可以只是局部填写的)。在终端驱动程序方面(例如在第11章中所述的 tcflush 函数),刷新表示丢弃已存在缓存中的数据。这里的刷新( flush )是指标准 I/O 缓存的写操作。缓存可由标准 I/O 例程自动地刷新(例如当填满一个缓存时) ,或者可以调用函数 fflush 刷新一个流。

    • Line Buffered .在这种情况下,当输入或者输出的时候遇到了一个换行符号的时候才会发生 i/o .一般终端的标准输入和标准输出会用到这种类型的缓存。

      对于这种 Line Buffered ,需要注意的是: Line Buffer 的缓存大小是固定的,当遇到换行符号之前缓存被填满了的时候,也会发生 i/o 。还有一个就是:当从一个 unbuffer 的方式进行请求输入的时候;以及当从 Line Buffered 请求输入的时候;这两种情况下也都会刷新( flushed )行缓存输出的缓存。

    • Unbuffered . standard i/o 不会缓存字符。一般标准错误流会使用这种类型的缓存,这样可以保证信息尽可能快地出现。

    ISO C 规定:

    • standard input /output = 当引用的不是交互式的设备的时候,是 =fully buffered .
    • standard error 一定不能是 fully buffered .

    但是,上面的规定却不是非常确定的,一般而言:

    • standard errorunbuffered .
    • 其他的 stream 如果它们引用的是 terminal 设备,那么就是 line Buffered ;否则是 fully buffered .

    (2)设置缓存

    如果不是用默认的缓存类型,有两个可以设置缓存的函数:

    #include <stdio.h>
    void setbuf(FILE *restrict fp, char *restrict buf);
    int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
    

    这两个函数必须在 streamopen 之后,并且任何 i/o 操作之前,被调用。

    使用 setbuf 我们可以设置和取消 buffer .如果设置 buffer ,指向 buf 的指针的数据的大小必须是 BUFSIZ ,一般这样 stream 就是 fully buffered .但是如果 stream 是和 terminal 相关联的也有些系统会把 buffered 设置为 line buffered 的。如果取消 buffer ,那么 buf 参数是 NULL 的。

    使用 setvbuf ,我们可以精确地指定我们想要的 buffer 类型。我们通过 modde 参数来进行指定:

    • _IOFBF: 表示 fully buffered .
    • _IOLBF: 表示 line buffered .
    • _IONBF: 表示 unbuffered .

    如果我们指定为 unbuffered ,那么 bufsize 参数会被忽略.

    如果指定了 fully buffered 或者 line buffered 那么:我们可以通过 bufsize 来设置缓存和大小。如果这时候的 buf 参数是 NULL 那么 standio = 会自动分配一个合适的大小,例如 =BUFSIZ .

    注意,你需要保证在 stream closed 的时候, buf 是存在的。如果我们自行指定分配的 buffer 是一个函数内部的局部变量,我们需要在这个函数返回之前把这个 streamclosed 了,另外,因为某些实现使用这个 buffer 的一部分做为记录的空间,所以我们存放在这个 buffer 中的数据应该小于它的实际大小。一般我们应该让系统自己选择分配合适的缓存和大小。

    有些C库使用 stat 结构的 st_blksize 作为最优输入输出缓存大小,通过后面的讨论我们能够看出, GNU C 库就是使用这个方法。

                                    关于setbuf和setvbuf函数
    +--------------------------------------------------------------------------------------------------------+
    | Function |  mode  |    buf    |          Buffer and length           |        Type of buffering        |
    |----------+--------+-----------+--------------------------------------+---------------------------------|
    |          |        | non-null  | user buf of length BUFSIZ            | fully buffered or line buffered |
    |setbuf    |        |-----------+--------------------------------------+---------------------------------|
    |          |        | NULL      | (no buffer)                          | unbuffered                      |
    |----------+--------+-----------+--------------------------------------+---------------------------------|
    |          |        | non-null  | user buf of length size              |                                 |
    |          |_IOLBF  |-----------+--------------------------------------|fully buffered                   |
    |          |        | NULL      | system buffer of appropriate length  |                                 |
    |          |--------+-----------+--------------------------------------+---------------------------------|
    | setvbuf  |        | non-null  | user buf of length size              |                                 |
    |          |_IOFBF  |-----------+--------------------------------------|line buffered                    |
    |          |        | NULL      | system buffer of appropriate length  |                                 |
    |          |--------+-----------+--------------------------------------+---------------------------------|
    |          | _IONBF | (ignored) | (no buffer)                          | unbuffered                      |
    +--------------------------------------------------------------------------------------------------------+
    

    我们需要注意,如果我们以一个函数中的自动变量为标准输入输出分配缓存,那么我们需要在返回函数之前关闭流。另外,有些实现使用这个缓存的部分内容作为内部索引之用,所以实际存放于缓存中的数据会比缓存的大小要小。一般来说,我们让系统自己选择缓存大小,以及自动分配缓存。这样,当我们关闭流的时候,标准 I/O 库会自动释放缓存。

    我们可以用 fflush 函数将流刷新,其声明如下:

    #include <stdio.h>
    int fflush(FILE *fp);
    

    返回:如果成功返回0,如果错误返回 EOF

    fflush 会导致在 stream 中没有被 written 的数据被提交到 kernel 中去。特别地,如果 fpNULL ,那么这个函数会导致所有的输出流被刷新。

    译者注

    原文参考

    参考: APUE2/ch05lev1sec4.html

    相关文章

      网友评论

        本文标题:APUE读书笔记-05标准输入输出库(1)

        本文链接:https://www.haomeiwen.com/subject/bjqymhtx.html