cstdio 的源码学习分析 10- 格式化输入输出函数 fprintf--- 宏定义 / 辅助函数分析 01
cstdio 中的格式化输入输出函数
fprintf 函数的实现 vfprintf 中包含了相当多的宏定义和辅助函数,接下来我们一起来分析一下它们对应的源码实现。
函数逻辑分析---vfprintf
2.宏定义/辅助函数分析
(1).ARGCHECK---参数检查
入参:
s:vfprintf 的入参 stream 信息
format:vfprintf 的入参 format 信息
按顺序做如下的参数检查:
先调用 CHECK_FILE 进行参数检查,主要是保证 stream 指针不为 NULL,而且_flags 在有效范围内,不满足条件则返回-1(EOF);
通过_IO_NO_WRITES 位检查 stream 是否可写,如果不可写,那说明本次操作是非法的,将_IO_ERR_SEEN 置位,并将错误码置为 EBADF(Bad file descriptor)返回-1(EOF);
判断 format 指针是否为空,如果为空,则将错误码置为 EINVAL(Invalid argument),返回-1(EOF).
(2).UNBUFFERED_P---判断 stream 是否是 unbufferd 状态,即不使用缓存 buffer
入参:
s:vfprintf 的入参 stream 信息
判断逻辑也比较简单,检查 FILE 对象的_flags 信息就好,即_IO_UNBUFFERED 是否置位
(3).PARSE_FLOAT_VA_ARG---解析浮点数变参变量
入参:
struct printf_info 结构体
定义在 glibc/stdio-common/printf.h 文件中,通过定义我们可以看到,里面主要是我们前文我们提到的
%[flags][width][.precision][length]specifier
表达式中各个特殊字段的信息。
与该宏一同定义的还有一个 PARSE_FLOAT_VA_ARG_EXTENDED,根据是否有 long double 类型数据做了一层封装,主要的解析工作如下:
从宏展开的上下文来看,根据 is_long_double 和 mode_flags 的置位情况(是否置位 PRINTF_LDBL_USES_FLOAT128---0x0008)决定当前是否要解析为 long double 类型
a. 如果是 long double 类型,那么首先将 is_binary128 置位 1(注意初始化时是 0)
b. 调用 va_arg 宏,将下一个可变参数解释为_Float128,并将值赋给 the_arg.pa_float128
va_arg 的一种实现方式,实际上就是将当前地址按照对应参数 type 解释,并将 ap 指针移到下一个位置
#define va_arg(ap,t) ((t)((ap+=sizeof(t))-sizeof(t)))
2.否则调用 PARSE_FLOAT_VA_ARG 进行实现,即非 128 位 long double 的情况
a. 这时 is_binary128 要保持置 0 状态
b. 根据是否是 long double 类型,决定参数是按照 long double 解析赋值给 the_arg.pa_long_double,还是按照 double 解析赋值给 the_arg.pa_double
(4).SETUP_FLOAT128_INFO---设置 is_binary128 flag 信息
入参:
struct printf_info 结构体
主要的逻辑判断如下:
通过 mode_flags 的置位情况(是否置位 PRINTF_LDBL_USES_FLOAT128---0x0008)决定当前是否将 is_binary128 置为 is_long_double;
其他情况统一置 0
(5).done_add_func---给最后输出的累加字符数做加法
入参
length:本次打印输出的字符长度
done:已有的输出字符长度
出参:累加之后的结果
从这个函数可以很明显地看出来 Glibc 函数的处理规范(做好参数判断和处理):
首先判断 done 是否小于 0,如果小于 0,说明是个异常值,直接返回,不做任何处理;
调用 INT_ADD_WRAPV 完成加法操作(注意如果溢出返回 1),如果溢出,那么将错误码置为 EOVERFLOW,返回-1,如果加法操作正常,则正常返回计算结果 ret
(6).done_add---给最后输出的累加字符数做加法
入参:
val:一个 int 类型的数据,与上一个中的 length 变量类似
这个宏展开实际上是调用了 done_add_func 完成的,但是在这个函数里面也做了重要的事情:
参数 val 的类型检测:
a. 首先检测 val 的 size 大小是否和 int 类型一致,不一致则 assert 报错;
b. 其次调用__typeof__获取 val 的类型,并使用该类型强转-1(补码表示为 0xFFFFFFFF),如果是无符号类型,则没有符号位,0xFFFFFFFF 则是表示最大的正整数,不可能大于 0,所以这里要求 val 必须是有符号数,否则 assert 报错;
调用 done_add_func 进行加法运算,检查返回值 done,上面说如果 done 本来是负数,或者出现溢出,done_add_func 都会返回负数,那么这里就会直接走到 all_done 标号处,解锁,释放资源,结束函数
后续函数作用分析见后文。
版权声明: 本文为 InfoQ 作者【桑榆】的原创文章。
原文链接:【http://xie.infoq.cn/article/1fcf8883083793fcaff3047db】。文章转载请联系作者。
评论