Linux 中 printf 函数不定长参数的实现

  1. 先看一下printf函数的原型.

    char print_buf[BUF_SIZE];
    void printf(const char *fmt, ...)
    {
        va_list ap; //定义一个指针变量
        unsigned int i;
        
        va_start(ap, fmt);
        i = vsprintf(print_buf, sizeof(print_buf), fmt, ap);
        va_end(args);
        
        __put_char(print_buf, i);
    }
    

    printf(const char *fmt, …)是一个可变参数函数, 第一个参数为字符串, 后面是格式化输出参数列表. C语言中函数的参数都是压进栈中的(参数压栈方向是从右往左!), 可变参数的函数必须有一个参数表示参数的个数, 才能让编译器知道压栈多少参数, 以及函数返回时弹出多少个参数, 我们在fmt字符串中提取’%‘的个数, 以及针对’%'后面不同的字符来处理.

    1. va_list类型

      va_list 的 va 是variable-argument(可变参数). list 就是列表的意思

      typedef char *va_list;		//Linux内核中va_list的实现
      va_list 	ap;
      
    2. va_start

      va_start(ap, A)宏将ap初始化,以便随后被va_arg()和va_end()使用,必须先调用。

      #define va_start(ap, A)	 (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))		// Linux 内核中宏定义原型
      
      #define va_start(ap,fmt) ( ap = (va_list)&fmt + _INTSIZEOF(fmt) )
      

      第一个参数ap, 就是刚刚提到的va_list类型, 第二个参数A, 就是一个确定的类型, 也就是"…"的类型, 他会将这些类型的地址放到va_list中, 根据初始化A来初始化AP.

      注意: 传给宏va_start 的参数fmt是可变参数列表中的前一个参数, ap指向变参列表中的第一个参数地址.

      注意: 函数参数压栈时, 参数的入栈顺序时从右往左, 出栈时是从左往右. 函数调用时, 先把若干个参数都压入栈中, 再压fmt, 最后压pc, 这样一来, 栈顶指针偏移找到了fmt, 通过fmt中的%占位符, 取后面参数的个数, 从而正确获得所有参数.

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

      计算int类型按4字节对齐后的结果. 通过_INTSIZEOF(n), 可以根据一个变量的类型计算变量再内存中占用的字节数, 从而正确定位参数在内存的位置.

    3. va_arg

      va_arg(ap, T)宏扩展为一个表达式, 该表达式具有调用中下一个参数的类型和值.

      va_arg()宏在va_start()宏之后的第一次使用会返回最后的参数. 连续的调用会返回其余参数的值.

      #define va_arg(ap, T) 	(*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADN    BND))))// Linux 内核中宏定义原型
      
      #define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )
      

      ap 是一个va_list类型, T是一个类型. 从参数列表中取出一个参数, 类型就是T.

    4. va_end

      va_end 是一个宏

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

      通过这个宏, 可以清楚的看到, 将va_list类型的ap赋值为NULL.

    5. __put_char() 将字符逐个打印到串口输出寄存器中.

      void __put_char(char *p, int num)
      {
          while(*p && num--)
          {
              *(volatile unsigned int *)0xd00000020 = *p++;
          };
      }
      
    6. vsprintf(char *buf, const char *fmt, va_list args) 函数, 将变量列表args中的参数按照fmt中规定的格式保存到临时缓冲buf中.

      int vsprintf(char *buf, canst char *fmt, va_list args) 
      {
      	unsigned NUM_TYPE num;
      	int base;
      	char*str;
      	int flags;
      	int field_width;
      	int Precision;
      	int qualifier;
      	str = bUf;
      
      	for (; *fmt ; ++fmt) 
      	{
      		if (* fmt ! = ' % ' )
      		{
      			*str++ = *fmt;
      			continue;
      		}
      
      		/* process flags */
      		flags = 0;
      		repeat:
      		++fmt;/* skip first "%" */
      
      		switch(*fmt)
      		{
      			case '- ' : flags |= LEFT;goto repeat;
      			case '+ ' : flags |= PLUS;goto repeat;
      			...
      		}
      		...
      		base = 10;
      
      		switch (*fmt){
      		case 'c':
      			...
      			*str++ (unsigned char)va_arg(args,int);
      			...
      		continue;
      
      
      	case 's':
      		str = string(str,va_arg(args, char *),field_width,precision,flags);
      		continue;
      		...
      
      		case ' X ' :
      			base = 16;
      		break;
      
      		case 'd':
      
      
      		case ' i '
      			flags |= SIGN;
      
      		case 'u':
      			break;
      
      		default:
      			* str++ ='%';
      		if (*fmt)
      			*str++ = *fmt;
      		else
      			--fmt ;
      			continue;
      		}
      		str = number (str, num, base, field_width, precision, flags) ;
      
      	}
      	*str == '\0';
      
      	return str-buf;
      }
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值