glibc sprintf源码梳理(上)

尝试阅读sprint源码
glibc 版本2.11

0. sprintf和snprintf

sprintf和snprintf的是字符串格式化方法,可将格式化数据写入字符串中。
snprintf规定了写入字符串的最大长度,避免字符串长度溢出

sprintf和snprintf方法定义在libio/stdio.h中,其声明和方法参数说明如下:

/*
    __s : 字符数组指针,存储拼接后的字符串
    __format: 字符串,规定了字符串格式
    ... : 可变参数,填充format中的值
    返回值: 欲写入的字符串长度
*/
int sprintf(char *__restrict __s, __const char *__restrict __format, ...)

/*
    __maxlen: 规定了拼接字符串的最长长度
            (1) 如果拼接后字符串长度 < maxlen,则正常复制,并且末尾添加('\0')
            (2) 如果拼接后字符串长度 > maxlen,则只复制(maxlen-1)个字符,末尾添加('\0')
    返回值: 欲写入的字符串长度,可能>maxlen
*/
int snprintf (char *__restrict __s, size_t __maxlen, __const char *__restrict __format, ...) 

1. stdio-common/sprintf.c

sprintf方法的实现在stdio-common/sprintf.c文件中,其中定义了__sprintf的实现,并sprintf是__sprintf的别名。

#define vsprintf(s, f, a) INTUSE(_IO_vsprintf) (s, f, a)

/* Write formatted output into S, according to the format string FORMAT.  */
/* VARARGS2 */
int
__sprintf (char *s, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = vsprintf (s, format, arg);
  va_end (arg);

  return done;
}
ldbl_hidden_def (__sprintf, sprintf)
ldbl_strong_alias (__sprintf, sprintf)
ldbl_strong_alias (__sprintf, _IO_sprintf)

这里说明一下ldbl_hidden_def, ldbl_strong_alias两个宏。
libc_hidden_def标志修饰的函数在动态链接的过程中进行延迟绑定,好处是只有该函数在调用到的时候才进行地址绑定。详解可阅读[glibc源码-libc_hidden_def]

weak_alias和strong_alias,两个都使用typeof起别名,weak_alias会带一个weak的函数属性,函数的weak属性意味着,如果用户自定义同名函数,能够重写原函数,在[Declaring Attributes of Functions]中有详细说明。

// ldbl_strong_alias 通过宏定义为 strong_alias
#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
#define ldbl_weak_alias(name, aliasname) weak_alias (name, aliasname)

// strong_alias通过typeof定义别名
#define strong_alias(name, aliasname) _strong_alias(name, aliasname)
#define _strong_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((alias (#name)));

#define weak_alias(name, aliasname) _weak_alias (name, aliasname)
#define _weak_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));

vsprintf的原名为_IO_vsprintf,找到_IO_vsprintf的定义在libio/iovsprintf.c中。

在此方法中,strong_alias给函数__sprintf一个强符号的别名_IO_sprintf

同时此方法中宏定义了vsprintf 为 __IO_vsprintf,INTUSE可以忽略,在shared code宏定义下INTUSE会改变括号内的名称。

2. libio/iovsprintf.c

__IO_vsprintf方法的定义在libio/iovsprintf.c文件中


int
__IO_vsprintf (char *string, const char *format, _IO_va_list args)
{
  _IO_strfile sf;
  int ret;

#ifdef _IO_MTSAFE_IO
  sf._sbf._f._lock = NULL;
#endif
  _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
  _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
  _IO_str_init_static_internal (&sf, string, -1, string);
  ret = INTUSE(_IO_vfprintf) (&sf._sbf._f, format, args);
  _IO_putc_unlocked ('\0', &sf._sbf._f);
  return ret;
}
INTDEF2(__IO_vsprintf, _IO_vsprintf)

ldbl_strong_alias (__IO_vsprintf, _IO_vsprintf)
ldbl_weak_alias (__IO_vsprintf, vsprintf)

分析一下__IO_vsprintf做了哪些事情,_IO_strfile sf创建了一个_IO_strfile的结构体

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer;
  _IO_free_type _free_buffer;
};
struct _IO_streambuf
{
  struct _IO_FILE _f;
  const struct _IO_jump_t *vtable;
};
typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;

_IO_no_init定义在libio/genops.c中,进行了文件标志位初始化等操作,_IO_JUMPS指向虚表:

#define _IO_JUMPS(THIS) (THIS)->vtable

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

_IO_str_init_static_internal定义在libio/strops.c中,进行了一些类似于将string和sf绑定的操作。

_IO_putc_unlocked,判断当前put指针是否到达put area的end位置,是则调用 __ovcerflow,将_ch(’\0’)写入当前指针,否则指针++指向_ch字符

#define _IO_putc_unlocked(_ch, _fp) \
   (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \
    ? __overflow (_fp, (unsigned char) (_ch)) \
    : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch))
    

关注的主线仍是__IO_vfprintf的内部实现,去寻找字符串拼接的实现代码。


3.stdio-common/vfprintf.c

_IO_vfprintf在stdio-common/vfprintf.c定义,stdio-common/vfprintf.c中有一个vfprintf的方法实现,_IO_vfprintf通过宏定义,别名,与vfprintf绑定

// 用 _IO_vfprintf_internal 声明了 vfprint
# define vfprintf	_IO_vfprintf_internal

// 给_IO_vfprintf_internal取了别名 _IO_vfprintf
ldbl_strong_alias (_IO_vfprintf_internal, vfprintf);
ldbl_hidden_def (_IO_vfprintf_internal, vfprintf)
ldbl_strong_alias (_IO_vfprintf_internal, _IO_vfprintf)

vfprintf实现方法,大约有1700多行,其中为的字符串拼接的细节。内容比较复杂,下一篇中进行分析。


/* The function itself.  */
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap)
{
    ...
}
4.小节

glibc的代码确实阅读起来困难很大,宏定义与别名使方法的调用关系与实现变得很复杂,同时glic会针对不同cpu架构,对某些方法有具体的重写,所以需要找到基础的实现方式。

但好消息是glic的源码中你可以找到一切,耐心的查找和梳理,终究能够分析出其实现的具体方式。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值