可變參數函數——以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\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

發佈了98 篇原創文章 · 獲贊 41 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章