采用C语言编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定。典型的例子有大家熟悉的函数printf()、scanf()和系统调用execl()等。那么它们是怎样实现的呢?
C编译器通常提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括va_start、va_arg和va_end等。在讲解以上宏之前我们先了解一下调用函数时传入参数的处理过程。
一、函数传入参数过程
一个函数包括函数名、传入参数、返回参数以及函数体,函数体编译后的二进制代码储存在程序代码区。当用户调用某个函数时,系统会通过函数名(C++中会涉及到mangled命名处理)查找函数体入口指针并压入栈中,之后将传入参数以从右至左的顺序压入栈中(栈空间是往低地址方向增长的,也即栈底对应高地址,栈顶对应低地址),示例如下:
#include <iostream> using namespace std; void fun(int a, ...) { int *temp = &a; temp++; for (int i = 0; i < a; ++i) { cout << *temp << endl; temp++; } } int main() { int a = 1; int b = 2; int c = 3; int d = 4; fun(4, a, b, c, d); system("pause"); return 0; } // Output: // 1 // 2 // 3 // 4
二、va_start、va_arg和va_end宏定义
接着我们再来看看va_start、va_arg和va_end等宏的具体定义。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是将类型n的大小向上取成4的倍数,如n为char型的话结果即为4 #ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是将v的地址重新解释成char*型 #else #define _ADDRESSOF(v) ( &(v) ) #endif #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define _crt_va_end(ap) ( ap = (va_list)0 ) #ifndef _VA_LIST_DEFINED #ifdef _M_CEE_PURE typedef System::ArgIterator va_list; #else typedef char * va_list; // vs2015中此句高亮 #endif /* _M_CEE_PURE */ #define _VA_LIST_DEFINED #endif // 以上宏定义出现在vadef.h,通过stdio.h即可使用
#define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end // 以上宏定义出现在stdarg.h中,若要使用则需加上 #include <stdarg.h>
三、va_start、va_arg和va_end使用示例
容易看出,以上宏定义主要涉及地址操作,va_start获取第二个传入参数的地址给va_list类型变量(假设为arg_ptr),va_arg用于获取当前参数值并将指针arg_ptr往后移,va_end则是将arg_ptr置为空。va_start、va_arg、va_end具体用法示例如下:
#include <stdio.h> #include <stdarg.h> int fun(int x, int y) { return x - y; } int fun(int count, ...) { va_list arg_ptr; // 等同于 char *arg_ptr; int nArgValue = count; int nArgCout = 0; va_start(arg_ptr, count); // 使arg_ptr指向第二个参数的地址 printf("The 1 th arg: %d ", nArgValue); // 输出第一个参数的值 int sum = 0; for (int i = 0; i < count; i++) { ++nArgCout; nArgValue = va_arg(arg_ptr, int);// 将arg_ptr所指参数返回成int并移动arg_ptr使其指向后一个参数,这里假设传入参数均是int型 printf("The %d th arg: %d ", i+2, nArgValue); // 输出各参数的值 sum += nArgValue; } va_end(arg_ptr); // 将arg_ptr置为空 return sum; } int main() { cout << fun(0) << endl; cout << fun(1, 1) << endl; // 优先匹配到函数int fun(int, int), 输出0 cout << fun(2, 3, 4) << endl; system("pause");
return 0;
}
// Output: // 0 // 0 // The 1 th arg: 2 // The 2 th arg: 3 // The 3 th arg: 4 // 7
注意:以上宏操作并不提供参数个数获取操作,这需要用户在函数中获取,如第二个fun函数使用count指明个数,printf通过解析第一个传入参数来确定参数个数与类型等。
参考资料:
百度百科-可变参函数