zoukankan      html  css  js  c++  java
  • 可变参数函数——以printf为例子

    一. 调用形式

    void foo(int argv1, char argv2, …)
    在参数表的末尾给出省略号,表明这个函数的参数是可变的

    二. 工作原理

    进程在调用函数时,会将函数参数压入用户栈,压入的顺序是从参数表右端开始,从右至左的压栈顺序支持了可变参数的实现。左边的参数在低地址,右边的参数在高地址。进入函数后,以左边的参数为线索,可透过指针依次访问右边省略掉的参数。
    Linux进程的虚拟存储器

    可变参数的实现一定会涉及到三个函数和一个变量,宏定义在stdarg.h中
    void va_start(va_list ap, last);
    type va_arg(va_list ap, type);
    void va_end(va_list ap);

    实现正确的函数调用,可遵循以下步骤:
    1.定义一个va_list类型变量,变量名为ap
    2.使用函数va_start(ap, last)对ap进行初始化,第一个参数是ap,第二个参数是省略号前一个变量,在有多个变量的情况下需注意。
    3.使用va_arg(ap, type)获取省略号中的可变参数,该可变参数的类型应为type,并将指针指向下一个可变参数
    4.在获取完所有的可变参数后(需要小心指针越界),用函数va_end(ap)关闭ap。

    下面是glibc中函数printf()的实现源码

    //glibc 2.14.1
    /* Write formatted output to stdout from the format string FORMAT.  */
    /* VARARGS1 */
    int
    __printf (const char *format, ...)
    {
      va_list arg;
      int done;
    
      va_start (arg, format);
      done = vfprintf (stdout, format, arg);
      va_end (arg);
    
      return done;
    }

    三. 例子

    我们可以自己实现简易版的myprintf()

    #include <stdio.h>
    #include <stdarg.h>
    
    //如果在嵌入式系统中,putchar(ch) 可换成串口输出函数
    #define console_print(ch)    putchar(ch)
    
    void    myprintf(char* fmt, ...);
    void    printch(char ch);
    void    printdec(int dec);
    void    printflt(double flt);
    void    printbin(int bin);
    void    printhex(int hex);
    void    printstr(char* str);
    
    int main(void)
    {
        myprintf("print: %c
    ", 'c');
        myprintf("print: %d
    ", 1234567);
        myprintf("print: %f
    ", 1234567.1234567);
        myprintf("print: %s
    ", "string test");
        myprintf("print: %b
    ", 0x12345ff);
        myprintf("print: %x
    ", 0xabcdef);
        myprintf("print: %%
    ");
        return 0;
    }
    
    void myprintf(char* fmt, ...)
    {
        double vargflt = 0;
        int vargint = 0;
        char* vargpch = NULL;
        char vargch = 0;
        char* pfmt = NULL;
        va_list vp;
    
        va_start(vp, fmt);
        pfmt = fmt;
    
        while(*pfmt)
        {
            if(*pfmt == '%')
            {
                switch(*(++pfmt))
                {
    
                    case 'c':
                        vargch = va_arg(vp, int); 
                        printch(vargch);
                        break;
                    case 'd':
                    case 'i':
                        vargint = va_arg(vp, int);
                        printdec(vargint);
                        break;
                    case 'f':
                        vargflt = va_arg(vp, double);
                        printflt(vargflt);
                        break;
                    case 's':
                        vargpch = va_arg(vp, char*);
                        printstr(vargpch);
                        break;
                    case 'b':
                    case 'B':
                        vargint = va_arg(vp, int);
                        printbin(vargint);
                        break;
                    case 'x':
                    case 'X':
                        vargint = va_arg(vp, int);
                        printhex(vargint);
                        break;
                    case '%':
                        printch('%');
                        break;
                    default:
                        break;
                }
                pfmt++;
            }
            else
            {
                printch(*pfmt++);
            }
        }
        va_end(vp);
    }
    
    void printch(char ch)
    {
        console_print(ch);
    }
    
    void printdec(int dec)
    {
        if(dec==0)
        {
            return;
        }
        printdec(dec/10);
        printch( (char)(dec%10 + '0'));
    }
    
    void printflt(double flt)
    {
        int icnt = 0;
        int tmpint = 0;
    
        tmpint = (int)flt;
        printdec(tmpint);
        printch('.');
        flt = flt - tmpint;
        tmpint = (int)(flt * 1000000);
        printdec(tmpint);
    }
    
    void printstr(char* str)
    {
        while(*str)
        {
            printch(*str++);
        }
    }
    
    void printbin(int bin)
    {
        if(bin == 0)
        {
            printstr("0b");
            return;
        }
        printbin(bin/2);
        printch( (char)(bin%2 + '0'));
    }
    
    void printhex(int hex)
    {
        if(hex==0)
        {
            printstr("0x");
            return;
        }
        printhex(hex/16);
        if(hex < 10)
        {
            printch((char)(hex%16 + '0'));
        }
        else
        {
            printch((char)(hex%16 - 10 + 'a' ));
        }
    }

    四. 注意事项

    1. 可变参数’…’里面的char会被提升为int,float会被提升为double。如果我们将va_arg(ap, int)改为va_arg(ap, char),系统会给出一个警告。详情可见[3].

    Waring: 'char' is promoted to 'int' when passed to through '...'

    2. 一定要使用arg_end(),可提高程序的移植性和健壮性。详情可见[4].

    【Reference】
    1.myprintf实现 http://blog.csdn.net/xfeng88/article/details/6695848
    2.stdarg相关函数 http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
    3.va_arg不可接受的类型 http://www.cppblog.com/ownwaterloo/archive/2009/04/21/unacceptable_type_in_va_arg.html
    4.va_end是必须的吗 http://www.cppblog.com/ownwaterloo/archive/2009/04/21/is_va_end_necessary.html

  • 相关阅读:
    [No0000161]IDEA初步接触
    [No0000171]wpf 类层次结构Class Hierarchy
    [No0000160]常用C# 正则表达式大全
    [No000015D]【李笑来 笔记整理】个人商业模式升级
    thinkphp 系统变量
    thinkphp不读取.env文件的键对值
    thinkphp 模板变量输出替换和赋值
    thinkphp 视图view
    thinkphp 响应对象response
    Thinkphp 请求和响应
  • 原文地址:https://www.cnblogs.com/season-peng/p/6713490.html
Copyright © 2011-2022 走看看