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)
复制代码
①.函数入参分析
②.局部变量申请
注意,为了帮助没有缓存 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 值,即当前输出的字符数。
(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. */
};
复制代码
①.函数入参分析
②.局部变量申请
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);
复制代码
⑦.返回结果
评论