虚竹|C语言标准IO的理解

虚竹|C语言标准IO的理解

输入输出(后面简称 “IO”)是应用程序不可或缺的一种基本能力 。 为了保持设计上的精简 , C 语言并没有在核心语言层面提供对 IO 相关接口的支持 , 相反 , 采用了标准库的方式来实现 。 通过引用名为 stdio.h 的标准库头文件 , 我们便可以快捷地为 C 程序添加读取用户键盘输入、输出内容到控制台 , 乃至读写文件等一系列常规的 IO 功能 。
快速回顾 IO 接口的使用方法首先 , 让我们通过下面这段代码来快速回顾 , 应该如何在 C 语言中使用这些由标准库提供的 IO 接口 。
#include <stdio.h>
int main(void) {
printf(\"Enter some characters:\\");
FILE* fp = fopen(\"./temp.txt\" \"w+\");
if (fp) {
char ch;
while (scanf(\"%c\" &ch)) {
if (ch == 'z') break;
putc(ch fp);

else {
perror(\"File open failed.\");

fclose(fp);
return 0;

这里 , 在 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 接口标准 。 如果我们将上面的示例程序完全用该标准进行重写 , 将会得到如下所示的代码:
#include <unistd.h>
#include <fcntl.h>
int main(void) {
const char str[
= \"Enter some characters:\\";
write(STDOUT_FILENO str sizeof(str));
const int fd = open(\"./temp.txt\" O_RDWR | O_CREAT);
if (fd > 0) {
char ch;
while (read(STDIN_FILENO &ch 1)) {
if (ch == 'z') break;
write(fd &ch sizeof(ch));

else {
const char errMsg[
= \"File open failed.\";
write(STDERR_FILENO errMsg sizeof(errMsg));

close(fd);
return 0;

可以看到 , 在使用低级 IO 接口进行编程时 , 我们需要处理与所进行 IO 操作有关的更多细节 。 比如 , 在调用 write 接口时 , 你必须要指定不同的文件描述符(File Descriptor) , 才能够区分所要进行的操作是“向屏幕上输出字符” , 还是“向文件内写入数据” 。 相反 , 在高级 IO 的实现中 , 我们并不需要关注这些细节 , 接口的名称可以直接反映其具体用途 。
两者之所以会在接口使用粒度上存在差异 , 是由于“低级 IO 与操作系统实现紧密相关” 。 对于 POSIX 标准来说 , 其所在系统会将绝大多数的 IO 相关资源 , 比如文档、目录、键盘、网络套接字 , 以及标准输入输出等 , 以“文件”的形式进行抽象 , 并使用相对统一的数据结构来表示 。 而在实际编码过程中 , 每一个可用的 IO 资源都会对应于一个唯一的整型文件描述符值 。 该值将被作为“单一可信源(The Single Source of Truth)” , 供相关接口使用 。