cstdio 的源码学习分析 10- 格式化输入输出函数 fprintf 整体分析
cstdio 中的格式化输入输出函数
stdio.h 中定义了一系列格式化输出函数,接下来我们一起来分析一下 fprintf 对应的源码实现。
固定参数类型
fprintf:将格式化数据写入文件流对象 FILE 中
fscanf::文件流对象 FILE 中读取格式化数据
printf:将格式化数据写入到 stdout(标准输出)FILE 对象中
scanf:从 stdin(标准输入)FILE 对象中读取数据
snprintf:将格式化数据输出到 buffer 中
sprintf:将格式化数据输出到字符串中
sscanf:从字符串中读取格式化数据
可变参数类型(函数与固定参数一一对应,只是接受可变参数列表作为输入或输出)
vfprintf:将格式化数据写入文件流对象 FILE 中
vfscanf::文件流对象 FILE 中读取格式化数据
vprintf:将格式化数据写入到 stdout(标准输出)FILE 对象中
vscanf:从 stdin(标准输入)FILE 对象中读取数据
vsnprintf:将格式化数据输出到 buffer 中
vsprintf:将格式化数据输出到字符串中
vsscanf:从字符串中读取格式化数据
函数作用简介---fprintf
将对应的 format 格式参数与附加的参数组成字符串,写入到指定的 FILE 对象中。
若写入成功,则返回写入的总字符数;
若写入失败,则将在 FILE 对象中值错误信息,该错误信息可由 ferror 函数检测到,返回一个负数;
若是多字节符号编码错误(如宽字符),则会将 errno 置为 EILSEQ,返回一个负数。
注意:上面参数中的 format 有许多规定好的格式
format 占位符的相关知识
format 占位符的一般形式:%[flags][width][.precision][length]specifier
specifier---特有类型
这是 format 占位符中最重要的部分,因为它标识了当前占位符的类型以及如何解释对应该位置的参数。
flags---格式化 flags 信息
width---格式化填充宽度
precision---精度信息
length---类型的长度扩展信息
如我们用 u 表示无符号整数,那么可以叠加 lu,表示 unsigned long int,llu 表示 unsigned long long int.
具体的组合如下,注意其中我们不常使用的如 jd 表示 intmax_t,td 表示 ptrdiff_t。
函数入口分析
这里通过 Glibc 的函数定义和调用关系,我们可以看到,__fprintf 是 fprintf 的别名,实际上最后是调用的__vfprintf_internal 函数,其实这也是符合我们的逻辑的,可变参数函数一定包含固定参数的函数,所以其实我们只需要实现一次可变参数函数就好。
注意:这里我们需要关注__vfprintf_internal 的入参需求:
FILE*是文件流对象指针
format 是我们设定的 format 参数
arg 是我们输入的附加参数的 va_list(这里可以参考https://xie.infoq.cn/article/af8d851c12035a63954c53d01 )
实际上经过 va_start 宏处理之后,arg 指向的就是 format 参数后面的位置,即第一个可变参数
0 是增加的 mode 信息
如何跳转到__vfprintf_internal 的实现?
Glibc 中为了做兼容操作,用了相当多的宏,我们接着追__vfprintf_internal 的定义,会发现原本的函数又跳到了 vfprintf,这里针对宽字符做了兼容处理,我们暂时只需要关注标准字符的处理即可。
至此,我们终于看到 fprintf 的函数最终函数实现,跳转路径如下:
fprintf->__fprintf->__vfprintf_internal->vfprintf
后续我们就实际分析 vfprintf 函数的实现流程
函数逻辑分析---vfprintf
1.整体分析
上一节中我们并未标识行号,大家可能对这个函数的大小没有足够的概念,这里我们将主要流程与相关行号的代码也一并贴出,可以看到是一个超过 500 行的函数,基本流程如下:
局部参数定义---包括所有用到的变量和扩展结构体;
各类参数预处理---包括参数检查,第一个 format 参数的识别等;
开始遍历处理 format 字符串直到识别到'\0';
a. 进行 format 的识别工作,要识别出上面所说的
%[flags][width][.precision][length]specifier
b. 处理当前识别出的 format,需要找到对应的参数,然后以对应的格式化信息进行处理
c. 将格式化后的数据写入约定的 buffer 中
一些 label 定义---如 do_positional,all_done;
返回结果
注意:这个超过 500 行的函数中还有许多子函数和宏的调用,所以是一个相对比较复杂的函数,这里我们需要一个一个部分地来进行分析。
总结
鉴于 vfprintf 这个函数的复杂性,笔者对文章进行了拆解,本文主要是对 fprintf 实现的整体分析,具体该函数中每一个步骤是如何完成的,如
有哪些宏的使用;
是如何识别出对应的 format 占位符的;
是如何构造我们最终的字符串的;
......
这些问题我们都留到后文一一细细分析,读者在本文的阅读中可以重点看看 format 参数的含义,以及 vfprintf 函数的 5 个大致流程(局部参数定义->各类参数预处理->遍历 format 字符串进行处理->label 定义->返回结果)。
版权声明: 本文为 InfoQ 作者【桑榆】的原创文章。
原文链接:【http://xie.infoq.cn/article/6935d842a839fff3fa4a75b6e】。文章转载请联系作者。
评论