可变参数函数——以printf为例子

Posted 海鲜小王子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了可变参数函数——以printf为例子相关的知识,希望对你有一定的参考价值。

一. 调用形式

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

二. 工作原理

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

可变参数的实现一定会涉及到三个函数和一个变量,宏定义在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\\n", 'c');
    myprintf("print: %d\\n", 1234567);
    myprintf("print: %f\\n", 1234567.1234567);
    myprintf("print: %s\\n", "string test");
    myprintf("print: %b\\n", 0x12345ff);
    myprintf("print: %x\\n", 0xabcdef);
    myprintf("print: %%\\n");
    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

以上是关于可变参数函数——以printf为例子的主要内容,如果未能解决你的问题,请参考以下文章

GO 可变参数

C++可变参数的另一种实现

可变参数使用

关于C中可变长参数

C语言可变参数函数详解示例

用于打印可变参数的宏,可选择无参数