要理解可变参数,首先要理解函数调用约定, 为什么只有__cdecl的调用约定支持可变参数,而__stdcall就不支持?
实际上__cdecl和__stdcall函数参数都是从右到左入栈,它们的区别在于由谁来清栈,__cdecl由外部调用函数清栈,而__stdcall由被调用函数本身清栈, 显然对于可变参数的函数,函数本身没法知道外部函数调用它时传了多少参数,所以没法支持被调用函数本身清栈(__stdcall), 所以可变参数只能用__cdecll.
另外还要理解函数参数传递过程中堆栈是如何生长和变化的,从堆栈低地址到高地址,依次存储 被调用函数局部变量,上一函数堆栈桢基址,函数返回地址,参数1, 参数2, 参数3...,相关知识可以参考我的这篇堆栈桢的生成原理。
有了上面的知识,我可以知道函数调用时,参数2的地址就是参数1的地址加上参数1的长度,而参数3的地址是参数2的地址加上参数2的长度,以此类推。
int Sum(int nCount, ...) { int nSum = 0; int* p = &nCount; for(int i=0; i<nCount; ++i) { cout << *(++p) << endl; nSum += *p; } cout << "Sum:" << nSum << endl << endl; return nSum; } string SumStr(int nCount, ...) { string str; int* p = &nCount; for(int i=0; i<nCount; ++i) { char* pTemp = (char*)*(++p); cout << pTemp << endl; str += pTemp; } cout << "SumStr:" << str << endl; return str; }在我们的测试函数中nCount表示后面可变参数的个数,int Sum(int nCount, )会打印后面的可变参数Int值,并且进行累加;string SumStr(int nCount, ...) 会打印后面可变参数字符串内容,并连接所有字符串。 然后用下面代码进行测试:
int main() { Sum(3, 10, 20, 30); SumStr(5, "aa", "bb", "cc", "dd", "ff"); system("pause"); return 0; }测试结果如下:
//typedef char * va_list; //#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(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) ) //#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //#define _crt_va_end(ap) ( ap = (va_list)0 ) //#define va_start _crt_va_start //#define va_arg _crt_va_arg //#define va_end _crt_va_end用系统的这些宏,我们的代码可以这样写了:
//use va_arg, praram is int int SumNew(int nCount, ...) { int nSum = 0; va_list vl = 0; va_start(vl, nCount); for(int i=0; i<nCount; ++i) { int n = va_arg(vl, int); // 注意va_arg第二个参数 cout << n << endl; nSum += n; } va_end(vl); cout << "SumNew:" << nSum << endl << endl; return nSum; } //use va_arg, praram is char* string SumStrNew(int nCount, ...) { string str; va_list vl = 0; va_start(vl, nCount); for(int i=0; i<nCount; ++i) { char* p = va_arg(vl, char*); // 注意va_arg第二个参数 cout << p << endl; str += p; } va_end(vl); cout << "SumStrNew:" << str << endl << endl; return str; }可以看到,其中 va_list实际上只是一个参数指针,va_start根据你提供的最后一个固定参数来获取第一个可变参数的地址,va_arg将指针指向下一个可变参数然后返回当前值, va_end只是简单的将指针清0. 用下面的代码进行测试:
int main() { SumNew(3, 1, 2, 3); SumStrNew(3, "12", "34", "56"); system("pause"); return 0; }测试结果如下:
//use va_arg, praram is std::string void SumStdString(int nCount, ...) { string str; va_list vl = 0; va_start(vl, nCount); for(int i=0; i<nCount; ++i) { string p = va_arg(vl, string); cout << p << endl; str += p; } va_end(vl); cout << "SumStdString:" << str << endl << endl; } int main() { string s1("hello "); string s2("world "); string s3("!"); SumStdString(3, s1, s2, s3); return 0; }测试结果如下:
可变参数多级调用示例:
#include <stdio.h> #include <stdarg.h> void func3(const char * format, ...) { va_list v3; va_start(v3, format); vprintf(format, v3); va_end(v3); } void func2(const char *format, va_list v2) { /*char szBuff[1024] = {0}; vsprintf(szBuff, format, v2); printf(szBuff);*/ vprintf(format, v2); } void func1(const char *format, ...) { va_list v1; va_start(v1, format); func2(format, v1); va_end(v1); } int main() { func1("%d %s\n", 1, "hello"); func3("%d %s\n", 2, "world"); return 0; }
参考资料:
1、C/C++中可变参数的原理