printf的实现原理

      要了解变参函数的实现,首先我们的弄清楚几个问题:
1:  该函数有几个参数。
2:  该函数怎样去访问这些参数。
3:  在访问完成后,如何从堆栈中释放这些参数。

函数变参

     对于c语言,它的调用规则遵循_cdedl调用规则。 在_cdedl规则中:

    1.  参数从右到左依次入栈
    2.  调用者负责清理堆栈
    3.  参数的数量类型不会导致编译阶段的错误

    printf的声明:   int _cdecl printf(const char* format, …);      // _cdecl是C和C++程序的缺省调用方式

     栈由高地址向低地址生长,又参数从右到左依次入栈,所以栈的高地址是printf最右边的参数。以

printf("%d %f %c %s\n", 3, 5.40, A, "hello world");为例,其栈的结构如下:

printf的实现原理

       在被调用的函数内部我怎么知道变参的类型是什么呢?对于printf函数来说,调用者通过第一个fmt参数中的%+格式字符的方式通知了被调用者(printf的实现者)。

 格式解析

       扫描format参数里的字符,如果是普通字符就打印输出,如果是%,就说明后面有可能是格式字符,需要进行检测,然后从栈低(其实是第一个参数的位置)弹出指定类型的数据,按照指定格式(十进制、十六进制、指定宽度、指定精度等等)进行输出。基本上是一个字符串解析的过程。

typedef char *va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )                          /* 1 */
#define va_start(va_list  ap, format) ( ap = (va_list)&format+ _INTSIZEOF(format) )           /* 2 */     
#define va_arg(va_list  ap,type)  
              ( *(type*)((ap += _INTSIZEOF(type)) -_INTSIZEOF(type)) )                                   /* 3 */
#define va_end(va_list  ap)  ( ap = (va_list)0 )                                                                      /* 4 */

1:  将sizeof(n)按sizeof(int)对齐

2:  初始化参数指针ap,将format右边第一个参数地址赋值给ap

3: type用来指名当前参数类型, 获得ap指向参数的值,同时使ap指向下一个参数

4: 在有些简单的实现中不起任何作用,在有些实现中可能会把ap改成无效值,这里把ap指针指向了 NULL

 

 c标准要求在同一个函数中va_start 和va_end 要配对的出现。那么到现在,处理多参数函数的步骤就是
1:首先是要保证该函数至少有一个参数,同时用...参数申明函数是变参函数。
2:在函数内部以va_start(ap,format)宏初始化参数指针。
3:用va_arg(ap,type)从左到右逐个取参数值。

 printf()格式转换的一般形式如下:

%[flags][width][.prec][type]
prec有一下几种情况:
                    正整数的最小位数
                    在浮点数中表示的小数位数
                    %g格式表示有效为的最大值
                    %s格式表示字符串的最大长度
                    若为*符号表示下个参数值为最大长度
width:为输出的最小长度,如果这个输出参数并非数值,而是*符号,则表示以下一个参数当做输出长度。

 

 另附一些比较有用的链接:

printf的原理

printf 函数的实现原理

C语言中可变参数函数实现原理

printf()用法详解

printf打印函数的原理浅析