前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >标准I/O库(ISO C的标准I/O库)

标准I/O库(ISO C的标准I/O库)

作者头像
zy010101
发布2020-05-09 17:42:26
1.2K0
发布2020-05-09 17:42:26
举报
文章被收录于专栏:程序员

本文讲述由ISO C定义的标准I/O库。这个库已经拥有非常长的历史了,它由D.R.在1975年左右编写,现在已经过去45年了。但是ISO C几乎没有对标准I/O库做出修改。不用我说,大家也知道这个库存在的问题应该是非常多的。

标准输入,标准输出,标准出错

Linux下的不带缓冲的I/O是围绕文件描述符来展开的。标准库的则不是,标准库的操作是围绕流(stream)这个概念来进行的。例如:标准输入流,标准输出流,标准出错流。这3个流是自动被进程使用的。他们其实和文件描述符STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO引用相同的文件。

带缓冲的I/O操作

使用文件描述符的I/O是不带缓冲的(当然了,这里所说的不带缓冲指的是进程中使用这两个函数不会自动缓冲,每使用一次就会进行一次系统调用,实际上除了原始磁盘I/O之外,其它的所有磁盘I/O都会经过内核缓冲区。),而标准I/O库为了减少read和write操作,使用了缓冲。

标准I/O提供了缓冲,但是成也萧何,败也萧何啊!这个缓冲的设计也是它的败笔吧!

标准I/O提供了3种缓冲方式。

全缓冲

在这种情况下,在填满标准I/O缓冲区以后,才进行I/O操作。在第一次执行I/O操作的时候,标准I/O会使用malloc来获取所需要的缓冲区。那么这时候有个问题,缓冲区没满就不进行实际的I/O操作,但是你想写入磁盘,怎么办?别慌,标准I/O设计了一个冲洗(flush)操作。你可以使用函数fflush来强制冲洗一个流。冲洗意味着将缓冲区的内容写到磁盘中。

函数fflush()

它有一个特殊情形,就是参数stream是NULL。这个时候表示强制冲洗所有输出流。

行缓冲

行缓冲就是当输入和输出中遇到换行符时,标准I/O执行实际I/O操作。当我们使用scanf和printf的时候,实际上就是行缓冲在起作用。行缓冲的长度是固定的,因此如果你在一行输入的内容过的,导致在你还没有换行的时候,也会发生实际的I/O操作。还有就是当你通过标准I/O库从一个不带缓冲或者是带行缓冲的流得到输入数据。那么就会强制冲洗所有行缓冲的输出流。

不缓冲

标准I/O对字符不进行缓冲。通常标准出错是不带缓冲的,这样就能使的出错信息及时打印出来。

ISO C的规则

  • 当且仅当标准输入和标准输出不指向交互式设备的时候,它们才是全缓冲的。
  • 标准错误一定不会是全缓冲。

规则就是如此的简单粗暴。它只说了什么时候全缓冲和不全缓冲。在Linux下。通常是这样的。

  • 标准错误是不带缓冲的。
  • 标准输入和标准输出,如果指向的设备是终端,那么使用行缓冲,否则使用全缓冲。

更改缓冲方式

我们可以使用下面的库函数来更改缓冲方式。

这些函数的只能在打开流之后调用。所以我们可以看到这些函数的第一个参数都是FILE *。需要注意的是setbuf(),setbuffer()以及setlinebuf其实都将调用setvbuf函数。因此,我们来关注一下setvbuf()函数。

也就是说buf和size是由mode决定的。但是当buf是NULL时,标准I/O会自动为该流分配适当长度的缓冲区(就是size所指定的值)。当然只有这个被指定的模式会受到影响,下次还是会新分配缓冲的。

其余的函数说明如下:

打开流操作

在Linux下这三个函数可以用来打开流。仔细观察可以发现fdopen()函数需要一个文件描述符做参数。而ISO C没有涉及文件描述符,所以只能在POSIX标准之下使用这个函数。另外对于fdopen()而言,它的mode参数的含义也略有不同。这是因为文件的权限在被open或者creat的时候已经指定好了。并且fdopen()函数并不能用来创建一个文件,很明显它需要一个文件描述符,既然有了文件描述符,那么文件肯定已经存在了。好了,下面我们先看一下mode的取值。

值得注意的是Linux内核并不区分文本文件和二进制文件。因此在Linux下使用带有b的参数是没有意义的(没有作用)。

读和写流

输入函数

标准I/O库提供了非常多的函数来进行读写操作。下面给出一些读写相关的函数。

有个问题需要注意,那就是返回值。

fgetc(),getc()和getchar()无论是遇到文件结尾还是错误都会返回同样的值。为了区分这两种情形,必须使用ferror或者feof函数。

clearerr()函数可以用来清楚1.出错标志,2.文件结束标志。上述的fileno函数可以被实现为宏。宏和函数的区别还是比较大的。在使用某些函数的时候,需要注意它是否被实现为宏,如果是,那么意味着一下几点:

1.参数不要具备副作用。

2.不能传递宏的地址,它没有地址。

3.宏比函数快。

输出函数

上述的函数之中,gets()函数由于没有指定缓冲区的大小。这曾造成过1988年的蠕虫事件。因此,当大多数人在Visual Studio2015之后的版本上书写C语言程序的时候,使用gets和scanf函数会报错。

VS不仅报错了,还让你使用scanf_s()函数来代替scanf函数。但是带来问题是比较差的可移植性。在某些类Unix操作系统上已经弃用了该接口。我们尽量不要使用不安全的函数。

二进制I/O

前面的I/O函数都是一次读写一行或者是单个字符,这在读写大文件的时候并不适合。为此,提供了下面的函数来执行二进制I/O操作。

这两个函数仍旧存在一些问题。那就是在不同的系统上工作的时候,可能由于struct对齐方式,以及是否遵从IEEE 754标准造成程序出错。多年之前,所有的Unix操作系统都运行在PDP-11计算机上,所以没有任何问题。

定位流

上述函数在类Unix系统上没有问题,但是如果在Window下可能就行不通。ISO C提供了fgetpos()和fsetpos()函数。

格式化I/O

格式化I/O能够漂亮的处理输入输出,但是格式转换符比较复杂,种类繁多。在此处不说明。只给出相关的函数。

在Unix中,标准I/O库最后还是需要调用不带缓冲的I/O函数。每个标准I/O都有一个与其相关联的文件描述符,可以使用fileno()函数来获得文件描述符。需要注意的是fileno()函数是POSIX标准提供的。

标准I/O的问题以及替代方式

前面已经说过了,标准I/O的历史已经非常长了,它存在问题也比较多。很明显标准I/O的效率不高。它需要在内核缓冲区复制一次数据,然后在用户进程内存中在复制一次数据。

另外的问题可能就是不够安全,微软已经在Windows平台提供了更加安全的函数。

在Linux下替代它们的可以有sfio库,以及使用mmap()函数的ASI包。

前文说过成也萧何,败也萧何。标准I/O使用的缓冲技术正是产生很多问题和混淆的地方。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/05/01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 标准输入,标准输出,标准出错
  • 带缓冲的I/O操作
    • 全缓冲
      • 函数fflush()
    • 行缓冲
      • 不缓冲
        • ISO C的规则
          • 更改缓冲方式
          • 打开流操作
          • 读和写流
            • 输入函数
              • 输出函数
              • 二进制I/O
              • 定位流
              • 格式化I/O
              • 标准I/O的问题以及替代方式
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档