尝试阅读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的源码中你可以找到一切,耐心的查找和梳理,终究能够分析出其实现的具体方式。