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

344 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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的信息

  1. 首先缓存原始stream信息到helper._put_stream;
  1. 如果是宽字符,我们需要初始化hp->_wide_data,调用_IO_wsetp将IO_buf_base设置为上面申请的临时buffer地址,然后将hp->_mode置为1;
  1. 如果是标准字符,需要调用_IO_setp设置IO_buf_base为上面的临时buffer地址,然后将hp->_mode置为-1;
  1. 将hp->_flags置为_IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK,即不可读+用户锁;
  1. 根据宏情况设置hp->_vtable_offset和hp->_lock;
  1. 使用s->_flags2初始化hp->_flags2;
  1. 将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完成的,但是要注意一下几点:

  1. 进行写入操作前后,需要对原始的文件流s进行lock保护;
  1. 标准字符和宽字符写入的buffer位置不同:

    1. 标准字符:hp->_IO_write_ptr - hp->_IO_write_base
    2. 宽字符:hp->_wide_data->_IO_write_ptr- hp->_wide_data->_IO_write_base
  1. 调用_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中的标志位,输出对应的字符:

  1. 首先输出一个L_('%');
  1. 按照各种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;
}