持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
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).
/* Sanity check of arguments. */
ARGCHECK (s, format);
#define ARGCHECK(S, Format) \
do \
{ \
/* Check file argument for consistence. */ \
CHECK_FILE (S, -1); \
if (S->_flags & _IO_NO_WRITES) \
{ \
S->_flags |= _IO_ERR_SEEN; \
__set_errno (EBADF); \
return -1; \
} \
if (Format == NULL) \
{ \
__set_errno (EINVAL); \
return -1; \
} \
} while (0)
#ifdef IO_DEBUG
# define CHECK_FILE(FILE, RET) do { \
if ((FILE) == NULL \
|| ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC) \
{ \
__set_errno (EINVAL); \
return RET; \
} \
} while (0)
#else
# define CHECK_FILE(FILE, RET) do { } while (0)
#endif
(2).UNBUFFERED_P---判断stream是否是unbufferd状态,即不使用缓存buffer
-
入参:
- s:vfprintf的入参stream信息
判断逻辑也比较简单,检查FILE对象的_flags信息就好,即_IO_UNBUFFERED是否置位
if (UNBUFFERED_P (s))
/* Use a helper function which will allocate a local temporary buffer
for the stream and then call us again. */
return buffered_vfprintf (s, format, ap, mode_flags);
#define UNBUFFERED_P(S) ((S)->_flags & _IO_UNBUFFERED)
(3).PARSE_FLOAT_VA_ARG---解析浮点数变参变量
-
入参:
- struct printf_info 结构体
定义在glibc/stdio-common/printf.h文件中,通过定义我们可以看到,里面主要是我们前文我们提到的
%[flags][width][.precision][length]specifier表达式中各个特殊字段的信息。
struct printf_info
{
int prec; /* Precision. */
int width; /* Width. */
wchar_t spec; /* Format letter. */
unsigned int is_long_double:1;/* L flag. */
unsigned int is_short:1; /* h flag. */
unsigned int is_long:1; /* l flag. */
unsigned int alt:1; /* # flag. */
unsigned int space:1; /* Space flag. */
unsigned int left:1; /* - flag. */
unsigned int showsign:1; /* + flag. */
unsigned int group:1; /* ' flag. */
unsigned int extra:1; /* For special use. */
unsigned int is_char:1; /* hh flag. */
unsigned int wide:1; /* Nonzero for wide character streams. */
unsigned int i18n:1; /* I flag. */
unsigned int is_binary128:1; /* Floating-point argument is ABI-compatible
with IEC 60559 binary128. */
unsigned int __pad:3; /* Unused so far. */
unsigned short int user; /* Bits for user-installed modifiers. */
wchar_t pad; /* Padding character. */
};
与该宏一同定义的还有一个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)))
-
否则调用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
struct printf_info info =
{
.prec = prec,
.width = width,
.spec = spec,
.is_long_double = is_long_double,
.is_short = is_short,
.is_long = is_long,
.alt = alt,
.space = space,
.left = left,
.showsign = showsign,
.group = group,
.pad = pad,
.extra = 0,
.i18n = use_outdigits,
.wide = sizeof (CHAR_T) != 1,
.is_binary128 = 0
};
PARSE_FLOAT_VA_ARG_EXTENDED (info);
#if __HAVE_FLOAT128_UNLIKE_LDBL
# define PARSE_FLOAT_VA_ARG_EXTENDED(INFO) \
do \
{ \
if (is_long_double \
&& (mode_flags & PRINTF_LDBL_USES_FLOAT128) != 0) \
{ \
INFO.is_binary128 = 1; \
the_arg.pa_float128 = va_arg (ap, _Float128); \
} \
else \
{ \
PARSE_FLOAT_VA_ARG (INFO); \
} \
} \
while (0)
#else
# define PARSE_FLOAT_VA_ARG_EXTENDED(INFO) \
PARSE_FLOAT_VA_ARG (INFO);
#endif
#define PARSE_FLOAT_VA_ARG(INFO) \
do \
{ \
INFO.is_binary128 = 0; \
if (is_long_double) \
the_arg.pa_long_double = va_arg (ap, long double); \
else \
the_arg.pa_double = va_arg (ap, double); \
} \
while (0)
(4).SETUP_FLOAT128_INFO---设置is_binary128 flag 信息
-
入参:
- struct printf_info 结构体
主要的逻辑判断如下:
- 通过mode_flags的置位情况(是否置位PRINTF_LDBL_USES_FLOAT128---0x0008)决定当前是否将is_binary128置为is_long_double;
- 其他情况统一置0
SETUP_FLOAT128_INFO (specs[nspecs_done].info);
#if __HAVE_FLOAT128_UNLIKE_LDBL
# define SETUP_FLOAT128_INFO(INFO) \
do \
{ \
if ((mode_flags & PRINTF_LDBL_USES_FLOAT128) != 0) \
INFO.is_binary128 = is_long_double; \
else \
INFO.is_binary128 = 0; \
} \
while (0)
#else
# define SETUP_FLOAT128_INFO(INFO) \
do \
{ \
INFO.is_binary128 = 0; \
} \
while (0)
#endif
(5).done_add_func---给最后输出的累加字符数做加法
-
入参
- length:本次打印输出的字符长度
- done:已有的输出字符长度
- 出参:累加之后的结果
从这个函数可以很明显地看出来Glibc函数的处理规范(做好参数判断和处理):
- 首先判断done是否小于0,如果小于0,说明是个异常值,直接返回,不做任何处理;
- 调用INT_ADD_WRAPV完成加法操作(注意如果溢出返回1),如果溢出,那么将错误码置为EOVERFLOW,返回-1,如果加法操作正常,则正常返回计算结果ret
/* Place to accumulate the result. */
int done;
return done_add_func (length, done);
/* Add LENGTH to DONE. Return the new value of DONE, or -1 on
overflow (and set errno accordingly). */
static inline int
done_add_func (size_t length, int done)
{
if (done < 0)
return done;
int ret;
if (INT_ADD_WRAPV (done, length, &ret))
{
__set_errno (EOVERFLOW);
return -1;
}
return ret;
}
// glibc/include/intprops.h
/* Store the low-order bits of A + B, A - B, A * B, respectively, into *R.
Return 1 if the result overflows. See above for restrictions. */
#if _GL_HAS_BUILTIN_ADD_OVERFLOW
# define INT_ADD_WRAPV(a, b, r) __builtin_add_overflow (a, b, r)
# define INT_SUBTRACT_WRAPV(a, b, r) __builtin_sub_overflow (a, b, r)
#else
# define INT_ADD_WRAPV(a, b, r) \
_GL_INT_OP_WRAPV (a, b, r, +, _GL_INT_ADD_RANGE_OVERFLOW)
# define INT_SUBTRACT_WRAPV(a, b, r) \
_GL_INT_OP_WRAPV (a, b, r, -, _GL_INT_SUBTRACT_RANGE_OVERFLOW)
#endif
(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标号处,解锁,释放资源,结束函数
int function_done = printf_unknown (s, &specs[nspecs_done].info);
...
done_add (function_done);
#define done_add(val) \
do \
{ \
/* Ensure that VAL has a type similar to int. */ \
_Static_assert (sizeof (val) == sizeof (int), "value int size"); \
_Static_assert ((__typeof__ (val)) -1 < 0, "value signed"); \
done = done_add_func ((val), done); \
if (done < 0) \
goto all_done; \
} \
while (0)
all_done:
/* Unlock the stream. */
_IO_funlockfile (s);
_IO_cleanup_region_end (0);
return done;
后续函数作用分析见后文。