C语言标准库深入浅出
名为 stdio.h 的标准库头文件,我们便可以快捷地为 C 程序添加读取用户键盘输入、输出内容到控制台,乃至读写文件等一系列常规的 IO 功能。
今天,我们来说一说C 语言中的标准 IO 模型,以及它背后的一些原理。
快速回顾 IO 接口的使用方法
首先,让我们通过下面这段代码来快速回顾,应该如何在 C 语言中使用这些由标准库提供的 IO 接口。
这里,在 main 函数内部,我们通过多种不同的方式,让程序与进程预设的 IO 流以及我们自行打开的 IO 流产生了交互。
其中,代码第 3 行,通过 printf 函数,我们可以将指定的文本传送至标准输出流(stdout)中。紧接着,借助代码第 4 行的 fopen 函数,我们得以在当前目录下打开名为 “temp.txt” 的文件,并将其与一个特定的文件 IO 流相关联。而当文件打开失败时,通过代码第 12 行的 perror 函数,我们能够将特定的错误信息传送到标准错误流(stderr)。最后,在代码的第 7 行,scanf 函数的调用可以让我们从标准输入(stdin)流中,读取从外部环境输入的信息。
IO 接口的不同级别
通常来说,IO 接口可以被分为不同层次。其中,C 语言提供的 IO 接口属于“标准 IO”的范畴。与其相对的,是名为“低级 IO”的另一套编程模型。顾名思义,低级 IO 会使用与具体操作系统相关的一系列底层接口来提供相应的 IO 能力,比如常用于 Unix 与类 Unix 操作系统上的 POSIX 接口标准。如果我们将上面的示例程序完全用该标准进行重写,将会得到如下所示的代码:
可以看到,在使用低级 IO 接口进行编程时,我们需要处理与所进行 IO 操作有关的更多细节。比如,在调用 write 接口时,你必须要指定不同的文件描述符(File Descriptor),才能够区分所要进行的操作是“向屏幕上输出字符”,还是“向文件内写入数据”。相反,在高级 IO 的实现中,我们并不需要关注这些细节,接口的名称可以直接反映其具体用途。
两者之所以会在接口使用粒度上存在差异,是由于“低级 IO 与操作系统实现紧密相关”。对于 POSIX 标准来说,其所在系统会将绝大多数的 IO 相关资源,比如文档、目录、键盘、网络套接字,以及标准输入输出等,以“文件”的形式进行抽象,并使用相对统一的数据结构来表示。而在实际编码过程中,每一个可用的 IO 资源都会对应于一个唯一的整型文件描述符值。该值将被作为“单一可信源(The Single Source of Truth)”,供相关接口使用。
而标准 IO 在接口设计与使用方式上,却不会与某类特定的操作系统进行“绑定”。相反,它会提供更加统一和通用的接口,来屏蔽底层不同系统的不同实现细节,做到“一次编写,到处编译”。
除此之外,即使上述两段采用不同级别 IO 接口实现的 C 代码,在实际的可观测执行效果方面基本一致,但它们在程序运行时,资源的背后使用逻辑上却有着较大的差异。
带缓冲的标准 IO 模型
那么,这两种 IO 模型除了在接口使用方式上有不同外,还有哪些重要差异呢?简单来讲,与低级 IO 相比,标准 IO 会为我们提供带缓冲的输入与输出操作。事实上,标准 IO 接口在实现时,会直接使用所在平台提供的低级 IO 接口。而低级 IO 接口在每次调用时,都会通过系统调用来完成相应的 IO 操作。
关于系统调用的内容,后面还会提到。在这里你只需要知道,系统调用的过程涉及到进程在用户模式与内核模式之间的转换,其成本较高。为了提升 IO 操作的性能,同时保证开发者所指定的 IO 操作不会在程序运行时产生可观测的差异,标准 IO 接口在实现时通过添加缓冲区的方式,尽可能减少了低级 IO 接口的调用次数。郑州看心理医生哪家好http://www.hyde8731.com/
让我们再把目光移回到之前的两段示例代码上。不知道你在运行对应的两段程序时,是否有观察到它们之间的差异呢?实际上,使用低级 IO 接口实现的程序,会在用户每次输入新内容到标准输入流中时,同时更新文件 “temp.txt” 中的内容。而使用标准 IO 接口实现的程序,仅会在用户输入的内容达到一定数量或程序退出前,再更新文件中的内容。而在此之前,这些内容将会被存放到缓冲区中。
当然,C 标准中并未规定标准 IO 接口所使用的缓冲区在默认情况下的大小,对于其选择,将由具体标准库实现自行决定。
除此之外,标准 IO 还为我们提供了可以自由使用不同缓冲策略的能力。对于简单的场景,我们可以使用名为 fflush 的接口,来在任意时刻将临时存放在缓冲区中的数据立刻“冲刷”到对应的流中。而在相对复杂的场景中,我们甚至可以使用 setvbuf 等接口来精确地指定流的缓冲类型、所使用的缓冲区,以及可以使用的缓冲区大小。
此时,再次编译并运行程序,其执行细节与之前相比会有什么不同?欢迎在评论区告诉我你的发现。