写点什么

cstdio 的源码学习分析 10- 格式化输入输出函数 fprintf--- 宏定义 / 辅助函数分析 05

作者:桑榆
  • 2022-10-17
    广东
  • 本文字数:4552 字

    阅读完需:约 1 分钟

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)
复制代码

①.函数入参分析

  • 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 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 的信息

  1. 首先缓存原始 stream 信息到 helper._put_stream;

  2. 如果是宽字符,我们需要初始化 hp->_wide_data,调用_IO_wsetp 将 IO_buf_base 设置为上面申请的临时 buffer 地址,然后将 hp->_mode 置为 1;

  3. 如果是标准字符,需要调用_IO_setp 设置 IO_buf_base 为上面的临时 buffer 地址,然后将 hp->_mode 置为-1;

  4. 将 hp->_flags 置为_IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK,即不可读+用户锁;

  5. 根据宏情况设置 hp->_vtable_offset 和 hp->_lock;

  6. 使用 s->_flags2 初始化 hp->_flags2;

  7. 将 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 完成的,但是要注意一下几点:

  1. 进行写入操作前后,需要对原始的文件流 s 进行 lock 保护;

  2. 标准字符和宽字符写入的 buffer 位置不同:

    标准字符:hp->_IO_write_ptr - hp->_IO_write_base

    宽字符:hp->_wide_data->_IO_write_ptr- hp->_wide_data->_IO_write_base

  3. 调用_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 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. */};
复制代码

①.函数入参分析

  • 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 中的标志位,输出对应的字符:

  1. 首先输出一个 L_('%');

  2. 按照各种 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;}
复制代码


发布于: 刚刚阅读数: 4
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020-02-29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
cstdio的源码学习分析10-格式化输入输出函数fprintf---宏定义/辅助函数分析05_源码刨析_桑榆_InfoQ写作社区