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 intbuffered_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 intbuffered_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)};#elsestatic 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 intprintf_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);
复制代码
⑦.返回结果
评论