带变长参数的函数

带变长参数的函数

 

很多语言都支持带变长参数的函数,C也不例外,我们常用的比如printf()函数,它的函数原型中参数列表里面有一个省略号,就代表了可变参数列表,可以先看下它的实现:

int printf(char *fmt, ...)
{
    static char sprint_buf[1024];

    va_list args;              // 初始化指向可变参数列表的指针args
    int n;
    va_start(args, fmt);        //args指向可变参数表的起始位置
    n = vsprintf(sprint_buf, fmt, args);
    va_end(args);             // 将args复位
    write(1, sprint_buf, n); 
    return n;
}

可能你现在还不明白这段代码,但大概功能能看出来吧,先是把可变参数传给vsprintf,由它负责将内容输出到buf中,然后将buf打印到屏幕上。

但有人可能会对以va_开头的那几个函数可能会比较陌生,下面我列出了它们的函数原型:

#include <stdarg.h>
void va_start(va_list ap, argN);
type va_arg(va_list ap, type);
void va_copy(va_list dest, va_list src);
void va_end(va_list ap);

实际上,这几个函数都不是函数,而是宏定义:

typedef char *va_list;      

#define  _AUPBND        (sizeof (acpi_native_int) - 1)
#define  _ADNBND        (sizeof (acpi_native_int) - 1)
#define _bnd(X, bnd)    (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)   (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)      (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

 

从上面的宏看好像它们都是在计算一些偏移位置而已。OK,先回过头来看看函数调用的过程,然后就会明白这些偏移值的计算原理。

我们知道计算机中函数调用有两种方式,分别是stdcall与cdecl,它们的区别在于:

1、_stdcall 是Pascal程序的缺省调用方式,通常用于 Win32 API中。按从右至左的顺序压参数入栈。 在主调函数中负责压栈,在被调函数中返回前负责清理栈。 由于被调函数并不知道传进来的参数个数,因此 _stdcall不适合可变长度参数的函数。
2、_cdecl (The C default calling convention)是C/C++ 调用约定,也是按从右至左的顺序压参数入栈,并且由调用者把弹出栈,因此,实现可变参数的函数只能使用该调用约定。

显然,我们的可变参数使用了_cdecl协议调用函数,发生函数调用时,主调函数将参数按照从右往左的顺序压入堆栈,被调函数返回后,主调函数还要对栈进行清理。

 

看一个简单的例子:

#include <stdarg.h>

void fun(int n, ...)
{
    int i, temp;
    va_list arg;
    va_start(arg, n); 

for (i = 0; i < n; ++i) { temp = va_arg(arg, int);  // 取出参数 printf("%d\n", temp); } va_end(arg); } int main() { int a = 1; int b = 2; int c = 3; int d = 4; fun(4, a, b, c, d); return 0; }

首先,声明了一个 va_list(就是char*指针)的参数列表args,

va_list args;

然后用va_start 宏来获取参数列表中的参数,这里args参数是刚声明的一个char*指针,自不必多说,n是位于栈顶的固定参数。

 va_start(args, n);  

我们知道栈是由高地址向低地址生长,va_start使arg指向了第一个可选参数,

va_arg(args, int);  

每次调用va_arg之后(需要指定下一个参数的类型),args就指向下一个参数,这就是前面那一坨宏定义干的事情。

现在我们再来想想printf()函数,它的第一个参数是fmt,根据'%'就能确定后面的可变参数个数和每个参数的类型,这样就能通过va_arg依次从栈中定位到每个参数的位置。

 

(夜深了,还是先睡吧,未完待续。。。)

 

 

 

 

 

 

 

 

posted @ 2014-04-23 23:13  如果的事  阅读(1308)  评论(0编辑  收藏  举报