持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情
cstdio中的格式化输入输出函数
fprintf函数的实现vfprintf中包含了相当多的宏定义和辅助函数,接下来我们一起来分析一下它们对应的源码实现。
函数逻辑分析---vfprintf
2.宏定义/辅助函数分析
(17)buffered_vfprintf---针对没有缓存buffer的文件流对象的处理
在函数调用中,首先调用UNBUFFERED_P判断文件流对象是否有缓存buffer可以使用,如果没有,那需要调用buffered_vfprintf,分配一个临时的buffer,然后再重新调回vfprintf进行处理。
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);
/* Helper function to provide temporary buffering for unbuffered streams. */
static int
buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args,
unsigned int mode_flags)
①.函数入参分析
- FILE *s:文件流对象
- const CHAR_T *format:format字符串
- va_list args:可变参数列表
- unsigned int mode_flags:当前的mode
②.局部变量申请
注意,为了帮助没有缓存buffer的文件流对象申请缓存buffer,我们创建了一个helper_file的结构体,里面包含了一个struct _IO_FILE_plus _f,这个就是我们的临时文件流对象,FILE *_put_stream保存的是我们的原始文件流对象。
这里将hp初始化为helper._f的地址,方便后面进行相关的更改和赋值。
注意我们申请的一个局部变量CHAR_T buf[BUFSIZ],这就是后面我们需要使用到的临时buffer
static int
buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args,
unsigned int mode_flags)
{
CHAR_T buf[BUFSIZ];
struct helper_file helper;
FILE *hp = (FILE *) &helper._f;
int result, to_flush;
/* Helper "class" for `fprintf to unbuffered': creates a temporary buffer. */
struct helper_file
{
struct _IO_FILE_plus _f;
#ifdef COMPILE_WPRINTF
struct _IO_wide_data _wide_data;
#endif
FILE *_put_stream;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
};
③.调用ORIENT检查文件流的_mode信息
关于ORIENT的处理逻辑可以参考之前的文章
/* Orient the stream. */
#ifdef ORIENT
ORIENT;
#endif
④.初始化helper的信息
- 首先缓存原始stream信息到helper._put_stream;
- 如果是宽字符,我们需要初始化hp->_wide_data,调用_IO_wsetp将IO_buf_base设置为上面申请的临时buffer地址,然后将hp->_mode置为1;
- 如果是标准字符,需要调用_IO_setp设置IO_buf_base为上面的临时buffer地址,然后将hp->_mode置为-1;
- 将hp->_flags置为_IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK,即不可读+用户锁;
- 根据宏情况设置hp->_vtable_offset和hp->_lock;
- 使用s->_flags2初始化hp->_flags2;
- 将helper._f.vtable 初始化为_IO_helper_jumps,里面对应_IO_wdefault_xxx或_IO_default_xxx的系统函数。
/* Initialize helper. */
helper._put_stream = s;
#ifdef COMPILE_WPRINTF
hp->_wide_data = &helper._wide_data;
_IO_wsetp (hp, buf, buf + sizeof buf / sizeof (CHAR_T));
hp->_mode = 1;
#else
_IO_setp (hp, buf, buf + sizeof buf);
hp->_mode = -1;
#endif
hp->_flags = _IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK;
#if _IO_JUMPS_OFFSET
hp->_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
hp->_lock = NULL;
#endif
hp->_flags2 = s->_flags2;
_IO_JUMPS (&helper._f) = (struct _IO_jump_t *) &_IO_helper_jumps;
#ifdef COMPILE_WPRINTF
static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_wdefault_finish),
...
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#else
static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_default_finish),
...
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#endif
#define _IO_JUMPS(THIS) (THIS)->vtable
⑤.调用vfprintf
注意,这里调用的文件流对象换成了hp,即带有临时buffer的文件流对象,在调用完成vfprintf之后,输出的相关信息都会写入到上面申请的临时buffer中
/* Now print to helper instead. */
result = vfprintf (hp, format, args, mode_flags);
⑥.将buffer中的数据写入文件流对象s中
这里的主要逻辑都是调用_IO_sputn完成的,但是要注意一下几点:
- 进行写入操作前后,需要对原始的文件流s进行lock保护;
-
标准字符和宽字符写入的buffer位置不同:
- 标准字符:hp->_IO_write_ptr - hp->_IO_write_base
- 宽字符:hp->_wide_data->_IO_write_ptr- hp->_wide_data->_IO_write_base
- 调用_IO_sputn后需要判断写入的字节数是否等于写入的字节数,如果不等,说明写入失败,result赋值为-1
/* Lock stream. */
__libc_cleanup_region_start (1, (void (*) (void *)) &_IO_funlockfile, s);
_IO_flockfile (s);
/* Now flush anything from the helper to the S. */
#ifdef COMPILE_WPRINTF
if ((to_flush = (hp->_wide_data->_IO_write_ptr
- hp->_wide_data->_IO_write_base)) > 0)
{
if ((int) _IO_sputn (s, hp->_wide_data->_IO_write_base, to_flush)
!= to_flush)
result = -1;
}
#else
if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0)
{
if ((int) _IO_sputn (s, hp->_IO_write_base, to_flush) != to_flush)
result = -1;
}
#endif
/* Unlock the stream. */
_IO_funlockfile (s);
__libc_cleanup_region_end (0);
⑦.返回结果
返回结果没有被改变为-1的情况下,将为vfprintf的done值,即当前输出的字符数。
return result;
}
(18)printf_unkown---打印未知的format信息
如果遇到我们无法解析的format,我们就会调用该函数进行打印。
它会提供一种默认的方式对info的信息进行打印
LABEL (form_unknown):
{
int function_done = printf_unknown (s, &specs[nspecs_done].info);
/* Handle an unknown format specifier. This prints out a canonicalized
representation of the format spec itself. */
static int
printf_unknown (FILE *s, const struct printf_info *info)
{
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. */
};
①.函数入参分析
- FILE *s:文件流对象
- const struct printf_info *info:printf_info信息
②.局部变量申请
- 申请done作为输出字符数的计数;
- 申请work_buffer,其大小为对齐宽度与精度信息的最大值的3倍;
- 申请workend指针,并指向work_buffer的最后一个元素的下一个位置;
- 临时变量w,CHAR_T指针
workend int done = 0;
CHAR_T work_buffer[MAX (sizeof (info->width), sizeof (info->prec)) * 3];
CHAR_T *const workend
= &work_buffer[sizeof (work_buffer) / sizeof (CHAR_T)];
CHAR_T *w;
③.输出一些固定信息
根据print_info中的标志位,输出对应的字符:
- 首先输出一个L_('%');
- 按照各种flag信息输出对应的字符,注意:在判断填充字符是否为L_('0')后再决定输出L_('0')
outchar (L_('%'));
if (info->alt)
outchar (L_('#'));
if (info->group)
outchar (L_('''));
if (info->showsign)
outchar (L_('+'));
else if (info->space)
outchar (L_(' '));
if (info->left)
outchar (L_('-'));
if (info->pad == L_('0'))
outchar (L_('0'));
if (info->i18n)
outchar (L_('I'));
④.处理对齐宽度
如果对齐宽度不为0,则将info->width转换为字符串,依次输出字符。
注意:这里_itoa_word就是需要传入buffer的最末尾指针,返回buffer开始记录的起始地址
if (info->width != 0)
{
w = _itoa_word (info->width, workend, 10, 0);
while (w < workend)
outchar (*w++);
}
⑤.处理精度参数
如果精度参数不等于0,先输出L_('.'),然后将精度转换为字符串,依次输出字符。
if (info->prec != -1)
{
outchar (L_('.'));
w = _itoa_word (info->prec, workend, 10, 0);
while (w < workend)
outchar (*w++);
}
⑥.处理参数类型
如果参数类型不等于L_('\0'),那就直接输出该字符
if (info->spec != L_('\0'))
outchar (info->spec);
⑦.返回结果
all_done:
return done;
}