当前位置: 代码迷 >> 综合 >> printf 函数的实现原理
  详细解决方案

printf 函数的实现原理

热度:33   发布时间:2023-12-09 14:09:14.0
=====================================================================================
*
*       Filename:  printf.c
*
*    Description:  printf 函数的实现
*
*        Version:  1.0
*        Created:  2010年12月12日 14时48分18秒
*       Revision:  none
*       Compiler:  gcc
*
*         Author:  Yang Shao Kun (), cdutyangshaokun@163.com
*        Company:  College of Information Engineering of CDUT
*
* =====================================================================================

*/

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

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

                1.参数从右到左依次入栈
                2.调用者负责清理堆栈
                3.参数的数量类型不会导致编译阶段的错误
要弄清楚变参函数的原理,我们需要解决上述的3个问题,其中的第三个问题,根据调
用原则,那我们现在可以不管。

要处理变参函数,需要用到 va_list 类型,和 va_start,va_end,va_arg 宏定义。我
看网上的许多资料说这些参数都是定义在stdarg.h这个头文件中,但是在我的linux机
器上,我的版本是fedorea 14,用vim访问的时候,确是在 acenv.h这个头文件中,估
计是内核的版本不一样的原因吧!!!

上面的这几个宏和其中的类型,在内核中是这样来实现的: 

#ifndef _VALIST
#define _VALIST
typedef char *va_list;
#endif                /* _VALIST *//*
* Storage alignment properties
*/
#define  _AUPBND                (sizeof (acpi_native_int) - 1)
#define  _ADNBND                (sizeof (acpi_native_int) - 1)/*
* Variable argument list macro definitions
*/
#define _bnd(X, bnd)            (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)           (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)              (void) 0
#define va_start(ap, A)         (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))#endif                /* va_arg */ 

首先来看 va_list 类型,其实这是一个字符指针。

va_start,是使ap指针指向变参函数中的下一个参数。

我们现在来看_bnd 宏的实现:
首先:
typedef s32 acpi_native_int;
typedef int            s32;
看出来,acpi_native_int 其实就是 int 类型,那么,

#define  _AUPBND                (sizeof (acpi_native_int) - 1)
#define  _ADNBND                (sizeof (acpi_native_int) - 1)
这两个值就应该是相等的,都-等于:3==0x00000003,按位取反后的结果就是:0xfffff
ffc,因此,
_bnd(x,bnd)宏在32位机下就是
(((sizeof (X)) + (3)) & (0xfffffffc)),那么作用就很明显是取4的整数,就相当与
整数除法后取ceiling--向上取整。

回过头来看 va_start(ap,A),初始化参数指针ap,将函数参数A右边右边第一个参数地
址赋值给ap,A必须是一个参数的指针,所以,此种类型函数至少要有一个普通的参数
,从而提供给va_start ,这样va_start才能找到可变参数在栈上的位置。
va_arg(ap,T),获得ap指向参数的值,同时使ap指向下一个参数,T用来指名当前参数类
型。
va_end 在有些简单的实现中不起任何作用,在有些实现中可能会把ap改成无效值,这
里,是把ap指针指向了 NULL。
c标准要求在同一个函数中va_start 和va_end 要配对的出现。

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

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

//现在来看看我们的printf函数的实现,在内核中printf函数被封装成下面的代码:
static char sprint_buf[1024];int printf(const char *fmt, ...)
{va_list args;int n;va_start(args, fmt);//初始化参数指针n = vsprintf(sprint_buf, fmt, args);/*函数放回已经处理的字符串长度*/va_end(args);//与va_start 配对出现,处理ap指针if (console_ops.write)console_ops.write(sprint_buf, n);/*调用控制台的结构中的write函数,将sprintf_buf中的内容输出n个字节到设备*/return n;
}
vs_printf函数的实现代码是:
int vsprintf(char *buf, const char *fmt, va_list args)
{int len;unsigned long long num;int i, base;char * str;const char *s;/*s所指向的内存单元不可改写,但是s可以改写*/int flags;        /* flags to number() */int field_width;    /* width of output field */int precision;        /* min. # of digits for integers; maxnumber of chars for from string */int qualifier;        /* 'h', 'l', or 'L' for integer fields *//* 'z' support added 23/7/1999 S.H.    *//* 'z' changed to 'Z' --davidm 1/25/99 */for (str=buf ; *fmt ; ++fmt){if (*fmt != '%') /*使指针指向格式控制符'%,以方便以后处理flags'*/{*str++ = *fmt;continue;}/* process flags */flags = 0;repeat:++fmt;        /* this also skips first '%'--跳过格式控制符'%' */switch (*fmt){case '-': flags |= LEFT; goto repeat;/*左对齐-left justify*/case '+': flags |= PLUS; goto repeat;/*p plus with ’+‘*/case ' ': flags |= SPACE; goto repeat;/*p with space*/case '#': flags |= SPECIAL; goto repeat;/*根据其后的转义字符的不同而有不同含义*/case '0': flags |= ZEROPAD; goto repeat;/*当有指定参数时,无数字的参数将补上0*/}
//#define ZEROPAD    1        /* pad with zero */
//#define SIGN    2        /* unsigned/signed long */
//#define PLUS    4        /* show plus */
//#define SPACE    8        /* space if plus */
//#define LEFT    16        /* left justified */
//#define SPECIAL    32        /* 0x */
//#define LARGE    64        /* use 'ABCDEF' instead of 'abcdef' *//* get field width ----deal 域宽 取当前参数字段宽度域值,放入field_width 变量中。如果宽度域中是数值则直接取其为宽度值。 如果宽度域中是字符'*',表示下一个参数指定宽度。因此调用va_arg 取宽度值。若此时宽度值小于0,则该负数表示其带有标志域'-'标志(左靠齐),因此还需在标志变量中添入该标志,并将字段宽度值取为其绝对值。  */field_width = -1;if ('0' <= *fmt && *fmt <= '9')field_width = skip_atoi(&fmt);else if (*fmt == '*'){++fmt;/*skip '*' *//* it's the next argument */field_width = va_arg(args, int);if (field_width < 0) {field_width = -field_width;flags |= LEFT;}}/* get the precision-----即是处理.pre 有效位 */precision = -1;if (*fmt == '.'){++fmt;   if ('0' <= *fmt && *fmt <= '9')precision = skip_atoi(&fmt);else if (*fmt == '*') /*如果精度域中是字符'*',表示下一个参数指定精度。因此调用va_arg 取精度值。若此时宽度值小于0,则将字段精度值取为0。*/{++fmt;/* it's the next argument */precision = va_arg(args, int);}if (precision < 0)precision = 0;}/* get the conversion qualifier 分析长度修饰符,并将其存入qualifer 变量*/qualifier = -1;if (*fmt == 'l' && *(fmt + 1) == 'l'){qualifier = 'q';fmt += 2;}else if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L'|| *fmt == 'Z'){qualifier = *fmt;++fmt;}/* default base */base = 10;/*处理type部分*/switch (*fmt){case 'c':if (!(flags & LEFT))/*没有左对齐标志,那么填充field_width个空格*/while (--field_width > 0)*str++ = ' ';*str++ = (unsigned char) va_arg(args, int);while (--field_width > 0)/*不是左对齐*/*str++ = ' ';/*在参数后输出field_width个空格*/continue;/*如果转换参数是s,则,表示对应的参数是字符串,首先取参数字符串的长度,如果超过了精度域值,则取精度域值为最大长度*/case 's':s = va_arg(args, char *);if (!s)s = "";len = strnlen(s, precision);/*字符串的长度,最大为precision*/if (!(flags & LEFT))while (len < field_width--)/*如果不是左对齐,则左侧补空格=field_width-len个空格*/*str++ = ' ';for (i = 0; i < len; ++i)*str++ = *s++;while (len < field_width--)/*如果是左对齐,则右侧补空格数=field_width-len*/*str++ = ' ';continue;
/*如果格式转换符是'p',表示对应参数的一个指针类型。此时若该参数没有设置宽度域,则默认宽度为8,并且需要添零。然后调用number()*/case 'p':if (field_width == -1){field_width = 2*sizeof(void *);flags |= ZEROPAD;}str = number(str,(unsigned long) va_arg(args, void *), 16,field_width, precision, flags);continue;// 若格式转换指示符是'n',则表示要把到目前为止转换输出的字符数保存到对应参数指针指定的位置中。 // 首先利用va_arg()得该参数指针,然后将已经转换好的字符数存入该指针所指的位置case 'n':if (qualifier == 'l'){long * ip = va_arg(args, long *);*ip = (str - buf);}else if (qualifier == 'Z'){size_t * ip = va_arg(args, size_t *);*ip = (str - buf);}else{int * ip = va_arg(args, int *);*ip = (str - buf);}continue;//若格式转换符不是'%',则表示格式字符串有错,直接将一个'%'写入输出串中。 // 如果格式转换符的位置处还有字符,则也直接将该字符写入输出串中,并返回到继续处理 //格式字符串。case '%':*str++ = '%';continue;/* integer number formats - set up the flags and "break" */case 'o':base = 8;break;case 'X':flags |= LARGE;case 'x':base = 16;break;
// 如果格式转换字符是'd','i'或'u',则表示对应参数是整数,'d', 'i'代表符号整数,因此需要加上 
// 带符号标志。'u'代表无符号整数case 'd':case 'i':flags |= SIGN;case 'u':break;default:*str++ = '%';if (*fmt)*str++ = *fmt;else--fmt;continue;}/*处理字符的修饰符,同时如果flags有符号位的话,将参数转变成有符号的数*/if (qualifier == 'l'){num = va_arg(args, unsigned long);if (flags & SIGN)num = (signed long) num;}else if (qualifier == 'q'){num = va_arg(args, unsigned long long);if (flags & SIGN)num = (signed long long) num;}else if (qualifier == 'Z'){num = va_arg(args, size_t);}else if (qualifier == 'h'){num = (unsigned short) va_arg(args, int);if (flags & SIGN)num = (signed short) num;}else{num = va_arg(args, unsigned int);if (flags & SIGN)num = (signed int) num;}str = number(str, num, base, field_width, precision, flags);}*str = '\0';/*最后在转换好的字符串上加上NULL*/return str-buf;/*返回转换好的字符串的长度值*/
}



  相关解决方案