函数中的不定参数
以前一直没去了解不定参数的使用,今天学习使用了一把:
int sum(int n, ...)
{
va_list arglist;
int result = 0;
int i, value;
va_start(arglist, n);
printf("(");
for (i = 0; i < n-1; i++)
{
value = va_arg(arglist, int);
result += value;
printf("%d+", value);
}
value = va_arg(arglist, int);
result += value;
printf("%d) = %d\n", value, result);
va_end(arglist);
return result;
}
这个是很简单直观的使用方法,像下面的调用:
sum(1, 10);
sum(2, 10, 20);
sum(3, 10, 20, 30);
sum(5, 1, 2, 3, 4, 5);
输出结果为:
(10) = 10
(10+20) = 30
(10+20+30) = 60
(1+2+3+4+5) = 15
使用不定参数需要用到 va_list 类型,和下面 3 个宏
- va_start(ap, v)
- va_arg(ap, t)
- va_end(ap)
首先,需要定义一个 va_list 类型的变量,va_list 类型实质是 char * 类型:
typedef char * va_list;
宏 va_start(ap, v) 是给 va_list 变量赋初值,ap 是 va_list 类型变量,v 是不定参数列表的前一个参数
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
va_start() 的作用是算出不定参数列表中的第 1 个参数的地址:
int sum(int n, ...)
------
|
+------------------> 根据不定参数列表前一个参数 n 的地址,算出不定参数列表中的第 1 个参数的地址
最简单的思维是 Address_Of(n) + Size_Of(n),其中 n 的地址很容易人 &n 取得,只是这个 Size_Of(n) 的获得需要费得心思:不可能直接用 sizeof 操作符来取。
不定参数中的数据类型可能是不一致的。或者有 int 类型,或者有 char 类型等等,它们的 sizeof 也就不一样。然而在 32 位代码下,processor 压入栈中的数据是对齐在 4 bytes 边界
即使是 char 类型的参数,push 入栈中也是 4 bytes 的(符号扩展到 4 bytes),因此不能简单地用 sizeof 取参数的字节数,应该要调整到下一个 4 bytes 边界上:
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
这个式子就是做这项工作,假设 n 是 5 bytes 大小的结构体类型参数,它压入栈中的空间是 8 bytes,通过调整到 n 参数地址的下一个 4 bytes 边界上,这个结果就正确了。
再来看看 va_arg(ap, t) 这个宏,它的作用是获得 ap 参数的值
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
ap 是经过 va_start() 宏后,设置的不定参数列表的第 1 个参数的地址,t 是这个参数的类型。我们看到 va_arg() 宏的另一个作用是再次设置为不定参数列表下一个参数,计算方法同 va_start() 是一致的。 经过 va_arg() 宏后 ap 的值就是下一个参数的地址。
最后,使用完不定参数后,va_end() 宏的作用是清 va_list 类型变量为 NULL(它是指针类型)
#define _crt_va_end(ap) ( ap = (va_list)0 )
不过,使用完后,即使不使用 va_end() 也完全没问题,它不像 malloc() 分配内存,必须使用 free() 释放内存,否则会发生内存泄漏。va_end() 将 va_list 类型变量清 0,不清 0 的话,我看不出有什么问题。