C语言printf函数实现解读

1 源码下载2 printf函数源码3 几个宏及解读va_listva_start函数参数进栈顺序栈区在计算机内部的地址情况_INTSIZEOF 宏

va_argva_end

4 自己实现的可变参数函数

1 源码下载

gun官网链接

按照这些步骤可以顺利的下载gnu的c语言源码,接下去我们看看printf函数。

2 printf函数源码

用vscode打开下载的源码,找到printf函数。 ( printf 函数路径为:glibc-2.36\stdio-common\printf.c ) 源码如下:

int

__printf (const char *format, ...)

{

va_list arg;

int done;

va_start (arg, format);

done = __vfprintf_internal (stdout, format, arg, 0);

va_end (arg);

return done;

}

#undef _IO_printf

ldbl_strong_alias (__printf, printf);

ldbl_strong_alias (__printf, _IO_printf);

可以看见主要是四个东西:va_list   va_start   va_end   __vfprintf_internal 前面三个先不看,后面重点介绍,先看看能不能看懂__vfprintf_internal 这个函数的实现。

在 glibc-2.36\stdio-common\vfprintf-internal.c 里可以看到这个函数实际上就是vfprintf函数, 也在相同的.c文件中

源码如下:

int

vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)

{

/* The character used as thousands separator. */

THOUSANDS_SEP_T thousands_sep = 0;

/* The string describing the size of groups of digits. */

const char *grouping;

/* Place to accumulate the result. */

int done;

/* Current character in format string. */

const UCHAR_T *f;

/* End of leading constant string. */

const UCHAR_T *lead_str_end;

/* Points to next format specifier. */

const UCHAR_T *end_of_spec;

/* Buffer intermediate results. */

CHAR_T work_buffer[WORK_BUFFER_SIZE];

CHAR_T *workend;

/* We have to save the original argument pointer. */

va_list ap_save;

/* Count number of specifiers we already processed. */

int nspecs_done;

/* For the %m format we may need the current `errno' value. */

int save_errno = errno;

/* 1 if format is in read-only memory, -1 if it is in writable memory,

0 if unknown. */

int readonly_format = 0;

/* Orient the stream. */

#ifdef ORIENT

ORIENT;

#endif

/* Sanity check of arguments. */

ARGCHECK (s, format);

#ifdef ORIENT

/* Check for correct orientation. */

if (_IO_vtable_offset (s) == 0

&& _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)

!= (sizeof (CHAR_T) == 1 ? -1 : 1))

/* The stream is already oriented otherwise. */

return EOF;

#endif

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);

/* Initialize local variables. */

done = 0;

grouping = (const char *) -1;

#ifdef __va_copy

/* This macro will be available soon in gcc's . We need it

since on some systems `va_list' is not an integral type. */

__va_copy (ap_save, ap);

#else

ap_save = ap;

#endif

nspecs_done = 0;

#ifdef COMPILE_WPRINTF

/* Find the first format specifier. */

f = lead_str_end = __find_specwc ((const UCHAR_T *) format);

#else

/* Find the first format specifier. */

f = lead_str_end = __find_specmb ((const UCHAR_T *) format);

#endif

/* Lock stream. */

_IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);

_IO_flockfile (s);

/* Write the literal text before the first format. */

outstring ((const UCHAR_T *) format,

lead_str_end - (const UCHAR_T *) format);

/* If we only have to print a simple string, return now. */

if (*f == L_('\0'))

goto all_done;

/* Use the slow path in case any printf handler is registered. */

if (__glibc_unlikely (__printf_function_table != NULL

|| __printf_modifier_table != NULL

|| __printf_va_arg_table != NULL))

goto do_positional;

/* Process whole format string. */

do

{

STEP0_3_TABLE;

STEP4_TABLE;

int is_negative; /* Flag for negative number. */

union

{

unsigned long long int longlong;

unsigned long int word;

} number;

int base;

union printf_arg the_arg;

CHAR_T *string; /* Pointer to argument string. */

int alt = 0; /* Alternate format. */

int space = 0; /* Use space prefix if no sign is needed. */

int left = 0; /* Left-justify output. */

int showsign = 0; /* Always begin with plus or minus sign. */

int group = 0; /* Print numbers according grouping rules. */

/* Argument is long double/long long int. Only used if

double/long double or long int/long long int are distinct. */

int is_long_double __attribute__ ((unused)) = 0;

int is_short = 0; /* Argument is short int. */

int is_long = 0; /* Argument is long int. */

int is_char = 0; /* Argument is promoted (unsigned) char. */

int width = 0; /* Width of output; 0 means none specified. */

int prec = -1; /* Precision of output; -1 means none specified. */

/* This flag is set by the 'I' modifier and selects the use of the

`outdigits' as determined by the current locale. */

int use_outdigits = 0;

UCHAR_T pad = L_(' ');/* Padding character. */

CHAR_T spec;

workend = work_buffer + WORK_BUFFER_SIZE;

/* Get current character in format string. */

JUMP (*++f, step0_jumps);

/* ' ' flag. */

LABEL (flag_space):

space = 1;

JUMP (*++f, step0_jumps);

/* '+' flag. */

LABEL (flag_plus):

showsign = 1;

JUMP (*++f, step0_jumps);

/* The '-' flag. */

LABEL (flag_minus):

left = 1;

pad = L_(' ');

JUMP (*++f, step0_jumps);

/* The '#' flag. */

LABEL (flag_hash):

alt = 1;

JUMP (*++f, step0_jumps);

/* The '0' flag. */

LABEL (flag_zero):

if (!left)

pad = L_('0');

JUMP (*++f, step0_jumps);

/* The '\'' flag. */

LABEL (flag_quote):

group = 1;

if (grouping == (const char *) -1)

{

#ifdef COMPILE_WPRINTF

thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,

_NL_NUMERIC_THOUSANDS_SEP_WC);

#else

thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);

#endif

grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);

if (*grouping == '\0' || *grouping == CHAR_MAX

#ifdef COMPILE_WPRINTF

|| thousands_sep == L'\0'

#else

|| *thousands_sep == '\0'

#endif

)

grouping = NULL;

}

JUMP (*++f, step0_jumps);

LABEL (flag_i18n):

use_outdigits = 1;

JUMP (*++f, step0_jumps);

/* Get width from argument. */

LABEL (width_asterics):

{

const UCHAR_T *tmp; /* Temporary value. */

tmp = ++f;

if (ISDIGIT (*tmp))

{

int pos = read_int (&tmp);

if (pos == -1)

{

__set_errno (EOVERFLOW);

done = -1;

goto all_done;

}

if (pos && *tmp == L_('$'))

/* The width comes from a positional parameter. */

goto do_positional;

}

width = va_arg (ap, int);

/* Negative width means left justified. */

if (width < 0)

{

width = -width;

pad = L_(' ');

left = 1;

}

}

JUMP (*f, step1_jumps);

/* Given width in format string. */

LABEL (width):

width = read_int (&f);

if (__glibc_unlikely (width == -1))

{

__set_errno (EOVERFLOW);

done = -1;

goto all_done;

}

if (*f == L_('$'))

/* Oh, oh. The argument comes from a positional parameter. */

goto do_positional;

JUMP (*f, step1_jumps);

LABEL (precision):

++f;

if (*f == L_('*'))

{

const UCHAR_T *tmp; /* Temporary value. */

tmp = ++f;

if (ISDIGIT (*tmp))

{

int pos = read_int (&tmp);

if (pos == -1)

{

__set_errno (EOVERFLOW);

done = -1;

goto all_done;

}

if (pos && *tmp == L_('$'))

/* The precision comes from a positional parameter. */

goto do_positional;

}

prec = va_arg (ap, int);

/* If the precision is negative the precision is omitted. */

if (prec < 0)

prec = -1;

}

else if (ISDIGIT (*f))

{

prec = read_int (&f);

/* The precision was specified in this case as an extremely

large positive value. */

if (prec == -1)

{

__set_errno (EOVERFLOW);

done = -1;

goto all_done;

}

}

else

prec = 0;

JUMP (*f, step2_jumps);

/* Process 'h' modifier. There might another 'h' following. */

LABEL (mod_half):

is_short = 1;

JUMP (*++f, step3a_jumps);

/* Process 'hh' modifier. */

LABEL (mod_halfhalf):

is_short = 0;

is_char = 1;

JUMP (*++f, step4_jumps);

/* Process 'l' modifier. There might another 'l' following. */

LABEL (mod_long):

is_long = 1;

JUMP (*++f, step3b_jumps);

/* Process 'L', 'q', or 'll' modifier. No other modifier is

allowed to follow. */

LABEL (mod_longlong):

is_long_double = 1;

is_long = 1;

JUMP (*++f, step4_jumps);

LABEL (mod_size_t):

is_long_double = sizeof (size_t) > sizeof (unsigned long int);

is_long = sizeof (size_t) > sizeof (unsigned int);

JUMP (*++f, step4_jumps);

LABEL (mod_ptrdiff_t):

is_long_double = sizeof (ptrdiff_t) > sizeof (unsigned long int);

is_long = sizeof (ptrdiff_t) > sizeof (unsigned int);

JUMP (*++f, step4_jumps);

LABEL (mod_intmax_t):

is_long_double = sizeof (intmax_t) > sizeof (unsigned long int);

is_long = sizeof (intmax_t) > sizeof (unsigned int);

JUMP (*++f, step4_jumps);

/* Process current format. */

while (1)

{

#define process_arg_int() va_arg (ap, int)

#define process_arg_long_int() va_arg (ap, long int)

#define process_arg_long_long_int() va_arg (ap, long long int)

#define process_arg_pointer() va_arg (ap, void *)

#define process_arg_string() va_arg (ap, const char *)

#define process_arg_unsigned_int() va_arg (ap, unsigned int)

#define process_arg_unsigned_long_int() va_arg (ap, unsigned long int)

#define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)

#define process_arg_wchar_t() va_arg (ap, wchar_t)

#define process_arg_wstring() va_arg (ap, const wchar_t *)

#include "vfprintf-process-arg.c"

#undef process_arg_int

#undef process_arg_long_int

#undef process_arg_long_long_int

#undef process_arg_pointer

#undef process_arg_string

#undef process_arg_unsigned_int

#undef process_arg_unsigned_long_int

#undef process_arg_unsigned_long_long_int

#undef process_arg_wchar_t

#undef process_arg_wstring

LABEL (form_float):

LABEL (form_floathex):

{

if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))

is_long_double = 0;

struct printf_info info =

{

.prec = prec,

.width = width,

.spec = spec,

.is_long_double = is_long_double,

.is_short = is_short,

.is_long = is_long,

.alt = alt,

.space = space,

.left = left,

.showsign = showsign,

.group = group,

.pad = pad,

.extra = 0,

.i18n = use_outdigits,

.wide = sizeof (CHAR_T) != 1,

.is_binary128 = 0

};

PARSE_FLOAT_VA_ARG_EXTENDED (info);

const void *ptr = &the_arg;

int function_done = __printf_fp_spec (s, &info, &ptr);

if (function_done < 0)

{

done = -1;

goto all_done;

}

done_add (function_done);

}

break;

LABEL (form_unknown):

if (spec == L_('\0'))

{

/* The format string ended before the specifier is complete. */

__set_errno (EINVAL);

done = -1;

goto all_done;

}

/* If we are in the fast loop force entering the complicated

one. */

goto do_positional;

}

/* The format is correctly handled. */

++nspecs_done;

/* Look for next format specifier. */

#ifdef COMPILE_WPRINTF

f = __find_specwc ((end_of_spec = ++f));

#else

f = __find_specmb ((end_of_spec = ++f));

#endif

/* Write the following constant string. */

outstring (end_of_spec, f - end_of_spec);

}

while (*f != L_('\0'));

/* Unlock stream and return. */

goto all_done;

/* Hand off processing for positional parameters. */

do_positional:

done = printf_positional (s, format, readonly_format, ap, &ap_save,

done, nspecs_done, lead_str_end, work_buffer,

save_errno, grouping, thousands_sep, mode_flags);

all_done:

/* Unlock the stream. */

_IO_funlockfile (s);

_IO_cleanup_region_end (0);

return done;

}

现在的水平看懂确实有一定的难度,于是就不看了。

3 几个宏及解读

前面提到了有几个其他的东西,现在我们来看看是什么

va_list

在vs2017下的定义是这样的:(\Community\VC\Tools\MSVC\14.16.27023\include\vadefs.h)

#ifndef _VA_LIST_DEFINED

#define _VA_LIST_DEFINED

#ifdef _M_CEE_PURE

typedef System::ArgIterator va_list;

#else

typedef char* va_list;

#endif

#endif

可以看出大部分情况下他就是 char * 类型,再看看百度的解释

va_start

这是 vs2017下x64的定义,看不了他的实现,那么就总结一下其他资料的解释。

这个宏的作用是:得到一个指向第一个可变参数的指针,也就是“”包裹的字符串后面的第一个参数。

看看这个宏的声明:

void va_start(va_list ap, last_arg);

ap: 这个参数的类型是之前提过的va_list,也就是那个指向可变参数列表的指针。 last_arg: 是最后一个固定参数,在printf函数中就是那个“”包裹的字符串。 在介绍他的实现之前,我们需要先补充三个知识:

函数传参进栈的顺序栈区在计算机内部的地址情况_INTSIZEOF 宏

函数参数进栈顺序

先说第一点,函数参数进栈的顺序      从右往左,依次进栈

func(int a, int b, int c, int d);

如果是这个函数的话,那么应该是d-> c -> b -> a 这是要了解的第一点。

void func(char *fmt, ...);

fmt肯定是在这一系列参数中最低的地址部分。

栈区在计算机内部的地址情况

来看这张经典的图:

栈底是高地址,栈顶是底地址,也就是说先进栈的会是高地址。之前那个例子中,地址从高到低依次也是 d , c, b, a。 在可变参数的函数中,大概就是这种情况:

|——————————————————————————| |最后一个可变参数 | ----------------高内存地址处 |——————————————————————————| … |——————————————————————————| |第N个可变参数 | -----------va_arg(arg_ptr,int)后arg_ptr所指的地方, | | 即第N个可变参数的地址。 |——————————————— | …………………………. |——————————————————————————| |第一个可变参数 | ------------ va_start(arg_ptr,start)后arg_ptr所指的地方 | | 即第一个可变参数的地址 |——————————————— | |——————————————————————————| | | |最后一个固定参数 | ------------- start的起始地址 |—————————————— —| … |—————————————————————————— | | | |——————————————— |-> 低内存地址处

_INTSIZEOF 宏

宏的定义:

#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

这个宏的作用是把sizeof(n) 向上取整作为 sizeof(int)的整数倍,用以在内存中对齐。 看过实现之后我觉得这个宏写的当精彩。

对于两个正整数 x, n 总存在整数 q, r 使得 x = nq + r, 其中 0<= r 0, 取 (q+1)n. 这也相当于把 x 表示为: x = nq + r’, 其中 -n < r’ <=0           //最大非正剩余 nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理: x+n = qn + (n+r’),其中 0

先试着理解上面的,如果上面那些步骤没看懂没关系。我们这样想,要把一个整数表达成另一个小的整数的整数倍,那么这个整数本身肯定只有两种情况:1,他可以被这个小整数整除,也就是正好在小整数划分区间的端点上。2,不可被整除,那就是落在某个区间上。我们要做的就是找到这个数的区间端点。 第一种情况: x正好是某个区间的端点,那么x+n-1落在他本身的区间,c语言中的/是向下取整的,除以n刚好就是某个端点值。 第二种情况,x在某个区间中,x+n必然在他下一个区间上。那么x+n-1,又有两种情况,一种是和x+n在同一个区间(x的下一个区间)上,第二种是在x下一个区间的左端点上,不可能和x在同一个区间。那么不管是哪一种情况x+n-1 进行c语言的向下整除操作后都会落在x的右端点上,就达到了向上取整的目的。

最后解释一下(x+n-1) & (~(n-1))的含义, 要执行x+n-1 除以n的操作, 相当于向右移动。那么我们有这个结论 若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。 而n若为2^m的话,二进制下就是1后接m个0,那么对n-1取反就是得到了m个0,在进行&操作就行了。

回到va_start宏:

#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) ) // 得到第一个可变参数的地址

结合前面那张栈的图,我们可以知道,最后一个固定参数的地址在第一个可变参数的地址下方,在给出固定参数的地址后,加上固定参数本身占用的内存后,得到了第一个可变参数的起始地址。

注意:宏va_start是对参数的地址进行操作的,要求参数地址必须是有效的。一些地址无效的类型不能当作固定参数类型。比如:寄存器类型,它的地址不是有效的内存地址值;数组和函数也不允许,他们的长度是个问题。因此,这些类型时不能作为va函数的参数的。

va_arg

先看宏的定义:

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

这个宏主要做了两件事情: 1、 指针ap指向下一个参数的地址 2、 强制类型转换后得到用户所指定的值

我们可以拆开来看这句话 将( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /* 指针ap指向下一个参数的地址 */ 拆成:

1. ap += _INTSIZEOF(t); // 当前,ap已经指向下一个参数了

2. return *(t *)( ap - _INTSIZEOF(t))

/* ap减去当前参数的大小得到当前参数的地址,再强制类型转换后返回它的值 */

用这个宏就进行可变参数的遍历操作,达到智能输出的效果了。

va_end

#define va_end(ap) ( ap = (va_list)0 )

这个宏很简单,就是将指针置空,而这个空间也不是在堆上的,也不用free了。

x86平台定义为ap=(char*)0;使ap不再> 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.

4 自己实现的可变参数函数

#include

#include

int k = -1, t = 0;

int it;

char *ct = NULL;

char cc = '0';

double dd = 0;

int it2 = 0;

int i = 0;

char tt[30];

void f(int it) {

while(it) {

t = it % 10;

tt[++k] = t + '0';

it /= 10;

}

}

void myprintf(char const* fmt, ...) {

char const *p;

va_list aq;

va_start(aq, fmt);

p = fmt;

while(*p != '\0') {

if(*p != '%') {

putchar(*p);

p++;

continue;

}

switch(*++p) {

case 'd':

it = va_arg(aq, int);

if(it < 0) {

putchar('-');

it = -it;

}

f(it);

for( ; k >= 0; k--) {

putchar(tt[k]);

}

break;

case 's':

ct = va_arg(aq, char *);

for( ; *ct; ct++) {

putchar(*ct);

}

break;

case 'c':

cc = va_arg(aq, int);

putchar(cc);

break;

case 'f':

dd = va_arg(aq, double);

if(dd < 0){

putchar('-');

dd = -dd;

}

it = (int)dd;

it2 = it;

dd = dd - it;

dd *= 1000000;

it = (int)dd;

for(i = k+1;i <= k+6 ;i++ ) {

tt[i] = '0';

}

f(it);

tt[k = i] = '.';

if(it2 == 0){

tt[++k] = '0';

}

f(it2);

for( ; k >= 0; k--) {

putchar(tt[k]);

}

break;

}

p++;

}

va_end(aq);

}

int main() {

myprintf("??%d, %s, >> %c, %d, %s , %f, %f", 10, "kkk", '&', 999, "aaaaa", 1234.15648777, 0.0012);

//myprintf("??%f", 0.0012);

return 0;

}

// ??10, kkk, >> &, 999, aaaaa , 1234.156487, 0.001200

算法还可以优化,代码也能更好看点,但学习的目的已经达到了

参考:

https://blog.csdn.net/blueskybluesoul/article/details/121969786https://www.cnblogs.com/saolv/p/7779364.htmlhttps://blog.csdn.net/zhyjunFov/article/details/12017697https://blog.csdn.net/weixin_45206746/article/details/117535332http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=814501

文章链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: