zoukankan      html  css  js  c++  java
  • 可变参数列表与printf()函数的实现

    问题

      当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时“道行”不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数存在区别,普通函数的参数在函数定义的时候就确定,而printf()函数的参数列表在调用时可变。还有一个原因导致我们没有去关注这个函数的实现,就是在编程的过程中很少用到参数列表可变的函数。的确是这样的,但是如果可以理解并内化,这将在编程过程中对某些功能实现带来很大的帮助。比如,在嵌入式设备开发中,可以利用设备的接口(UART、USB)编写出一套类似printf()函数功能的调试工具。

     

    printf()函数的实现

    static char sprint_buf[1024];

    int printf(char *fmt, ...)

    {

      va_list args;

      int n;

     

      va_start(args, fmt);

      n = vsprintf(sprint_buf, fmt, args);

      va_end(args);

      write(stdout, sprint_buf, n);

     

      return n;

    }

      咋一看,printf()函数的试下也不是太复杂,有几个陌生又关键的词语va_list、va_start、va_end、vsprintf。其实还有一个va_arg(stdarg.h),是参数列表可变的关键,而真正的打印实现是vsprintf()函数,在这里不讨论vsprintf()函数,只讨论三个宏定义。

    先看看stdarg.h文件下这三个宏的实现:

    第一种:

    typedef char  *va_list;

    #define va_start(ap,v)   ap = (va_list)&v + sizeof(v)

    #define va_arg(ap,t)      (((t *)ap)++[0])

    #define va_end(ap)

     

    第二种:

    typedef char  *va_list;  

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

    #define va_start(ap,v)        ( ap = (va_list)&v + _INTSIZEOF(v) )  

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

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

    _INTSIZEOF(n):为了字节对齐,将n的长度化为int长度的整数倍。

    在32位系统中,~(sizeof(int)–1) )展开为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int)–1) )后最后两位肯定为0,也就是4的整数倍。

     

    每个平台下的stdarg.h头文件的定义都不相同,但是意思都一样:

    (1) va_list:    定义一个va_list型的变量ap,也就是char *;

    (2) va_start:获取到可变参数表的首地址,并将该地址赋给指针ap;

    (3) va_arg:   获取当前ap所指向的可变参数,并将指针ap指向下一个可变参数。注意,该宏的第二个参数为类型;

    (4) va_end:   结束可变参数的获取。

     

    应用

    当理解了printf()函数的实现,便可以着手去实现嵌入式设备中的调试工具,其原理一样:

    int bsp_debug_printf(const char *fmt, ...)

    {

      int ret = -1;

     

      va_list ap;

      char buf[256];

     

       va_start(ap, fmt);

       (void)vsnprintf((char *)buf, sizeof(buf), fmt, ap);

      va_end(ap);

      bsp_puts(buf);

      ret = 0;

     

      return  ret;

    }

    以上只是简单的实现这个功能,并没有考虑硬件保护,返回打印字符数等。

     

    再看一例:

    找出最大值

    int MaxTest(int c, ...)  

    {  

      int max = c;  

      va_list ap;

                         

       va_start(ap,c);        

     

      c = va_arg(ap,int);    

      while(0 != c)  

      {  

        if(max < c) max = c;  

              c = va_arg(ap,int); 

      } 

      va_end(ap);

                         

      return max;  

    }   

      这个例子主要想利用va_list、va_start、va_arg、va_end这四个关键指令来实现可变参数列表的函数,它需要一个参数列表末尾标识(0)来告诉系统这参数列表的最后一个参数。

     

    还可以有另外一种方法来实现以上功能:

    int MaxTest1(int n, ...)

    {

           int max = n;

           int *p = &n + 1;

          

           while(0 != *p)

           {

                  if (max < *p) max = *p;

                  p++;

           }

          

           return max;

    }

      这个例子利用了函数参数的存储规则来实现对参数的获取。可变参数函数的实现与函数调用的栈结构有关,正常情况下C/C++的函数参数入栈规则从右到左的,即函数中的最右边的参数最先入栈。

      这种方法看似效果相同,但是稍有不慎就会带来灾难。若printf()函数由这种方法实现,而没有边界的检查,当堆栈越界访问时极可能导致程序崩溃。

  • 相关阅读:
    java jdk1.8 32/64位 官方绿色版下载附安装教程
    坡度常用的表示方法
    就此道别
    阿里巴巴矢量图标库(iconfont)批量全选的方法
    thinkphp6.0 集成Alipay 手机和电脑端支付的方法
    法定的属于我的第23个年头已经结束,在今天迎来第24年的第一天。
    世界地图展开图,来自 Simon's World Map
    thinkphp6.0 composer 安装 web-token/jwt-framework 常见出错原因分析及解决方法
    thinkphp6 常用方法文档
    Python获取列表中的最后一个或者倒数第几个的方案
  • 原文地址:https://www.cnblogs.com/guanguangreat/p/6119278.html
Copyright © 2011-2022 走看看