第5章——《标准I/O库》

实验环境介绍

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64

流和FILE对象

  • 多字节和单字节:ascii字符集是单字符的,但是如果使用国际字符集,一个字符需要用多个字节表示。
  • 一个流在最初被创建时,并没有定向。如果要在一个未定向的流上使用一个多字节I/O函数,需要将流设置为宽定向。
    fwide
  • 在一个未定向的流上使用单字节I/O操作函数,则该流就被设置成字节定向。如果使使用多字节操作函数,则该流被设置成宽定向
  • 测试代码如下
# 单字符IO测试
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <wchar.h>
#include <locale.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: ./a.out [filename]\n");
        exit(EXIT_FAILURE);
    }

    FILE *file = fopen(argv[1], "w+");
    if (!file) {
        fprintf(stderr, "Usage: %s is NULL\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    // wchar_t w_str[] = L"精灵宝可梦abcd";
    char s_str[] = "精灵宝可梦abcd";
    // setlocale(LC_ALL, "zh_CN.UTF-8");
    // wprintf(L"%ls/n", w);
    // fwprintf(file, L"%ls\n", w_str);
    fprintf(file, "%s\n", s_str);
    return 0;
}
result:
manjingliu@localhost part_5]$ ./fwide schar             
[manjingliu@localhost part_5]$ cat schar 
精灵宝可梦abcd
[manjingliu@localhost part_5]$ od -x schar 
0000000 b2e7 e7be b581 aee5 e59d af8f a2e6 61a6
0000020 6362 0a64
0000024


# 宽字符IO测试
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <wchar.h>
#include <locale.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: ./a.out [filename]\n");
        exit(EXIT_FAILURE);
    }

    FILE *file = fopen(argv[1], "w+");
    if (!file) {
        fprintf(stderr, "Usage: %s is NULL\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    wchar_t w_str[] = L"精灵宝可梦abcd";
    setlocale(LC_ALL, "zh_CN.UTF-8");
    // wprintf(L"%ls/n", w);
    fwprintf(file, L"%ls\n", w_str);
    return 0;
}

result:
[manjingliu@localhost part_5]$ ./fwide wchar 
[manjingliu@localhost part_5]$ cat wchar 
精灵宝可梦abcd
[manjingliu@localhost part_5]$ od -x wchar 
0000000 b2e7 e7be b581 aee5 e59d af8f a2e6 61a6
0000020 6362 0a64
0000024
  • 为什么 C 语言会有一个宽字符的类型?这要涉及到两个概念,内部表示(Internal representation)和外部表示(External representation),前者表示字符是如何保存在内存里面的,也就是字符在程序运行时的表现形式;而后者表示字符是如何存放在外部介质上以及在外部通信的表现形式。历史上这两个本是同一个概念,但是随着字符集的逐渐扩大,也就独立出两个概念。上面所说的编码方式全部是外部表示,也就是我们通常所说的某个文件采用何种编码。C语言定义的宽字符则是内部表示,即这些字符在程序中的表现形式。

    注意:如果一个流对象已经使用了宽字节io函数,就不要用单字节io函数操作了,不然会有输出异常

  • 什么时候用宽字符,什么时候不用

    • 如果你所需要的又只是简单的复制移动操作,那完全可以不用宽字符,但是如果需要诸如查找字符的个数、字符串排序等操作,就必须要转换成宽字符了。不过对复制移动操作,需要注意的是,当外部表示是用 UTF-8 的编码方式时,可以采用 str 组的字符串操作,而对于其他的编码,那就只能使用 mem 组的内存处理函数,否则会造成数据的丢失。

标注你输入、标准输出和标准错误

  • 忽略

缓冲

  • 标准I/O库提供缓冲的目的是为了尽可能减少使用read和write的调用次数。之前在第3章,我们已经测试过不同缓冲区对write的影响。
  • 标准I/O提供以下3种类型的缓冲
    • 全缓冲:这种情况下,只有填满标准I/O缓冲区后才进行实际I/O操作。一般磁盘上的文件通常由标准I/O库实施全缓冲。(缓冲区一般使用malloc获得需使用的缓冲区)。fflush函数可以冲洗一个流。flush代表两个意思:在标准I/O库方面,flush意味着将缓冲区的数据写到磁盘上。在终端驱动程序方面(tcflush函数),flush表示丢弃已经存储在缓冲区中的数据
    • 行缓冲:当输入和输出中遇到换行符的时,标准I/O库执行I/O操作进行实际I/O操作。行缓冲有两个限制:
      • 行缓冲的长度是固定的,如填满了,即使没有换行符也会进行I/O操作。
      • 任何时候只要通过标准I/O库从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它从内核请求需要数据,这些数据也坑你已经在缓冲区中,并不一定要从内核读取)得到输入数据,那么就会冲洗所有行缓冲输出流。(测试代码如下)
#include <stdlib.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
    char buffer[1024] = {0};
    printf("abcdefg"); // 标准输出为行缓冲
    gets(buffer); // 从标准输入读
    while (1)
        sleep(1);
    return 0;
}
result:
[manjingliu@localhost part_5]$ ./test_line_buffer 
abcdefg^C
* 不带缓冲:标准I/O库不对字符进行缓冲存储,不用管它们是否有一个换行符
* ISO C要求的缓冲特征:
    * 当且晋档标准输入和标准输出并不指向交互式设备时,它们才是全缓冲
    * 标准错误绝不会是全缓冲
    * 很多系统使用下列缓冲:
        * 标准错误是不带缓冲的
        * 如果流是指向终端设备的流,则是行缓冲。否则才是全缓冲
  • 更改流的缓冲类型
    缓冲类型
    • 实验证明当缓冲区已经被设置为无缓冲,mode为_IOLBK和_IONBF的时候,如果buf为NULL,缓冲类型无法设置成功
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

int
main(int argc, char *argv[])
{
    char buffer[1024] = {0};
    /* set stdout as NBF */
    setbuf(stdout, NULL);
    fprintf(stdout, "I am NBF");

    /* set stdout as LBF */
    // setbuf(stdout, buffer);
    if (setvbuf(stdout, buffer, _IOLBF, 1024))
    // if (setvbuf(stdout, NULL, _IOLBF, 1024))
        err_sys("setvbuf _IOLBF");
    fprintf(stdout, "I am _IOLBF");

    sleep(1);
    if (setvbuf(stdout, NULL, _IONBF, 0))
        err_sys("setvbuf _IONBF");
    fprintf(stdout, "I am _IONBF");

    while (1)
        sleep(1);
    exit(EXIT_SUCCESS);
}
result:第一句先打印

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

int
main(int argc, char *argv[])
{
    char buffer[1024] = {0};
    /* set stdout as NBF */
    setbuf(stdout, NULL);
    fprintf(stdout, "I am NBF");

    /* set stdout as LBF */
    // setbuf(stdout, buffer);
    // if (setvbuf(stdout, buffer, _IOLBF, 1024))
    if (setvbuf(stdout, NULL, _IOLBF, 1024))
        err_sys("setvbuf _IOLBF");
    fprintf(stdout, "I am _IOLBF");

    sleep(1);
    if (setvbuf(stdout, NULL, _IONBF, 0))
        err_sys("setvbuf _IONBF");
    fprintf(stdout, "I am _IONBF");

    while (1)
        sleep(1);
    exit(EXIT_SUCCESS);
}
result:前两句先打印
I am NBFI am _IOLBFI am _IONBF

缓冲区
* 如果在一个函数内部进行流缓冲区的设置,且这个缓冲区是自动变量类型的,那么在这个函数结束之前,得把文件关闭,测试如下:

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

void io_pr_alloced(FILE *file)
{
    char buffer[1024] = {0}; // 这里有隐患,函数结束后,该处内存不安全
    if (setvbuf(stdout, buffer, _IOLBF, 1024))
        err_sys("setvbuf _IOLBF");
    fprintf(file, "I am _IOLBF");
    // fclose(file);
}

int
main(int argc, char *argv[])
{
    FILE *file = fopen("buff.tmp", "w");
    io_pr_alloced(file);
    int i = 3;
    while (i--)
        sleep(1);
    exit(EXIT_SUCCESS);
}
result:
[manjingliu@localhost part_5]$ ./setbuf & cat buff.tmp      
[1] 110218
[manjingliu@localhost part_5]$

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

void io_pr_alloced(FILE *file)
{
    char buffer[1024] = {0}; // 这里有隐患,函数结束后,该处内存不安全
    if (setvbuf(stdout, buffer, _IOLBF, 1024))
        err_sys("setvbuf _IOLBF");
    fprintf(file, "I am _IOLBF");
    fclose(file);
}

int
main(int argc, char *argv[])
{
    FILE *file = fopen("buff.tmp", "w");
    io_pr_alloced(file);
    int i = 3;
    while (i--)
        sleep(1);
    exit(EXIT_SUCCESS);
}
result:
[manjingliu@localhost part_5]$ ./setbuf & cat buff.tmp      
[1] 110226
I am _IOLBF[manjingliu@localhost part_5]$

一般而言应该让系统自己选择缓冲区长度,并自动分配缓冲区。这种情况下关闭流,标准I/O库会自动释放缓冲区

打开流

打开流
* 忽略
* 可以通过fdopen将一个流与fd相关联

读和写流

读写
* 忽略

每次一行I/O

  • 忽略

标准I/O的效率

效率

二进制I/O

  • 使用二进制I/O可以一次性读一个二进制块,getc之类的,只能一个字节一个字节处理,很费时。fgets之类,遇到null就停止
    二进制io

定位流

  • 忽略

格式化I/O

格式化
* 测试代码如下

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

int
main(int argc, char *argv[])
{
    int ret = dprintf(1, "dprintf out");
    if (ret < 0)
        err_sys("dprintf");
    else
        printf("dprintf %d\n", ret);

    char buffer[1024] = {0};
    ret = snprintf(buffer, 10, "snprintf");
    if (ret < 0)
        err_sys("snprintf");
    else
        printf("snprintf %d\n", ret);

    while (1)
        sleep(1);
    exit(EXIT_SUCCESS);
}

result:
[manjingliu@localhost part_5]$ ./stdio_t 
dprintf outdprintf 11
snprintf 8
^C
  • printf变体,可变参数支持
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

#define LOG_FILE "ns.log"

FILE *log_file = NULL;

void
ns_console_log(char *format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    vprintf("\n", args);
    va_end(args);
}

void
ns_file_log(char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(log_file, format, args);
    vfprintf(log_file, "\n", args);
    va_end(args);
    fflush(log_file);
}


int
main(int argc, char *argv[])
{
    log_file = fopen(LOG_FILE, "a");
    if (!log_file) {
        fprintf(stderr, "fopen %s error\n", LOG_FILE);
        fclose(log_file);
        exit(EXIT_FAILURE);
    }

    ns_console_log("I am ns_console_log");
    ns_file_log("I am ns_file_log");
    fclose(log_file);
    exit(EXIT_FAILURE);
}

result:
[manjingliu@localhost part_5]$ ./log     
I am ns_console_log
[manjingliu@localhost part_5]$ cat ns.log 
I am ns_file_log

获取流的缓冲区大小

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdio_ext.h>

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

void pr_stream_type(const char *, FILE *);
int is_unbuffered(FILE *);
int is_linebuffered(FILE *);
int buffer_size(FILE *);

int
main(int argc, char *argv[])
{
    pr_stream_type("stdin", stdin);
    pr_stream_type("stderr", stderr);
    pr_stream_type("stdout", stdout);
    exit(EXIT_SUCCESS);
}

void
pr_stream_type(const char *name, FILE *fp)
{
    printf("stream = %s, ", name);
    if (is_unbuffered(fp))
        printf("unbuffered");
    else if (is_linebuffered(fp))
        printf("line buffered");
    else
        printf("fully buffered");

    printf(", buffer size = %d\n", buffer_size(fp));
}


#if defined(_IO_UNBUFFERED)
int
is_unbuffered(FILE *fp)
{
    return (fp->_flags & _IO_UNBUFFERED);
}

int
is_linebuffered(FILE *fp)
{
    return (fp->_flags & _IO_LINE_BUF);
}

int
buffer_size(FILE *fp)
{
    return (fp->_IO_buf_end - fp->_IO_buf_base);
}

#elif defined(_IONBF)

#ifdef _LP64
#define _flag __pad[4]
#define _ptr __pad[1]
#define _base __pad[2]
#endif

int
is_unbuffered(FILE *fp)
{
    return (fp->_flags & _IONBF);
}

int
is_linebuffered(FILE *fp)
{
    return (fp->_flags & _IOLBF);
}

int
buffer_size(FILE *fp)
{
#ifdef _LP64
    return (fp->_base - fp->_ptr);
#else
    return (BUFSIZ) /* just a guess */
#endif
}

#elif defined(__SNBF)
int
is_unbuffered(FILE *fp)
{
    return (fp->_flags & __SNBF);
}

int
is_linebuffered(FILE *fp)
{
    return (fp->_flags & __SLBF);
}

int
buffer_size(FILE *fp)
{
    return (fp->_bf._size);
}

#else
#error unknow stdio implementation!
#endif

result:
[manjingliu@localhost part_5]$ ./buffer_type 
stream = stdin, fully buffered, buffer size = 0
stream = stderr, unbuffered, buffer size = 0
stream = stdout, line buffered, buffer size = 1024

临时文件

  • mkstemp建立唯一的临时文件,用于进程间进行大量数据的传输
  • 在同一个程序中同时创建两个名字一样的临时文件会导致段错误(因为不唯一)
  • 临时文件名的后6位设置为xxxxxx,如:/tmp/dirXXXXXX
    • EINVAL 参数template 字符串最后六个字符非XXXXXX. EEXIST 无法建立临时文件

内存流

内存流
type参数
* 虽然仍然使用FILE知怎进行访问,但是并没有底层文件,所有的I/O都是通过在缓冲区与主存之间来回传送字节完成的
* 内存流非常适用于创建字符串。因为内存流脂肪纹主存,不访问磁盘上的文件,所以对于将I/O流作为临时文件的函数来说,会有很大的性能提升。

猜你喜欢

转载自blog.csdn.net/u012570105/article/details/81407302