C++可变参数的另一种实现
大家熟知的C库函数printf函数就是一个可变参数函数,它是怎么实现的呢?不过他实现是有条件的,必须函数参数的入栈顺序为从右向左的顺序,也即函数的形参,在函数调用之前,必须是最右边的参数先入栈,并且参数都必须通过栈传递,以1个例子说明,如函数func(arg1, arg2,arg3),那么函数的堆栈应是:
ebp是帧指针寄存器,一般用来存取堆栈,有了堆栈结构,下面我们看看C可变参数的具体实现原理:
#include <stdio.h> enum { ptChar, ptInt, ptFloat, ptDouble, }; void printSum(unsigned long paramFormat, ...) { /*高16位为可变参数类型,低16位为可变参数个数*/ int paramType = (paramFormat >> 16); int paramNum = paramFormat & 0xffff; /*¶mFormat = ebp + 8,第一个参数的地址*/ unsigned long *pArg = ¶mFormat; /*ebp + 0x0c, 第二个参数地址*/ pArg++; switch(paramType) { case ptChar: { int sum = 0; for (int i = 0; i < paramNum; i++) { char *pValue = (char *)pArg; sum += *pValue; pArg++; } printf("%d\n", sum); } break; case ptInt: { int sum = 0; for (int i = 0; i < paramNum; i++) { int *pValue = (int *)pArg; sum += *pValue; pArg++; } printf("%d\n", sum); } break; case ptFloat: { float sum = 0; /**/ pArg++; /*浮点参数,堆栈占8个字节,所以指针偏移为8*/ for (int i = 0; i < paramNum; i++) { float *pValue = (float *)pArg; sum += *pValue; pArg++; pArg++; } printf("%f\n", sum); } break; case ptDouble: { double sum = 0; /*双精度浮点参数,堆栈占8个字节,所以指针偏移为8*/ for (int i = 0; i < paramNum; i++) { double *pValue = (double *)pArg; sum += *pValue; pArg++; pArg++; } printf("%f\n", sum); } break; default: printf("unknowned type!\n"); break; } } void main() { unsigned long paramFormat = 3; char a = 1, b = 2, c = 3; printSum(paramFormat, a, b, c); paramFormat = ptInt << 16; paramFormat += 3; int ia = 1, ib = 2, ic = 3; printSum(paramFormat, ia, ib, ic); paramFormat = ptFloat << 16; paramFormat += 3; float fa = 1, fb = 2, fc = 3; printSum(paramFormat, fa, fb, fc); paramFormat = ptDouble << 16; paramFormat += 3; double da = 1, db = 2, dc = 3; printSum(paramFormat, da, db, dc); }
上面这种方法对函数参数的入栈顺序有限制,必须从右向左入栈,这就是为什么pascal调用方式不能实现printf的原因,并且函数形参都要通过栈来传递,这对有些编译器为了优化处理,函数参数通过寄存器来传递,从而不满足要求。鉴于次,本文采用C++的默认形参实现可变参数的方法,没有上面的这些限制,下面是实现代码:
#include <stdio.h> enum { ptChar, ptInt, ptFloat, ptDouble, }; void printSum(unsigned long paramType, void *arg1 = NULL, void *arg2 = NULL, void *arg3 = NULL, void *arg4 = NULL, void *arg5 = NULL, void *arg6 = NULL, void *arg7 = NULL, void *arg8 = NULL, void *arg9 = NULL, void *arg10 = NULL) { void *arg[10] = { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, }; switch(paramType) { case ptChar: { int sum = 0; for (int i = 0; i < 10; i++) { if (arg[i] != NULL) { char *pValue = (char *)arg[i]; sum += *pValue; } else break; } printf("%d\n", sum); } break; case ptInt: { int sum = 0; for (int i = 0; i < 10; i++) { if (arg[i] != NULL) { int *pValue = (int *)arg[i]; sum += *pValue; } else break; } printf("%d\n", sum); } break; case ptFloat: { float sum = 0; for (int i = 0; i < 10; i++) { if (arg[i] != NULL) { float *pValue = (float *)arg[i]; sum += *pValue; } else break; } printf("%f\n", sum); } break; case ptDouble: { double sum = 0; for (int i = 0; i < 10; i++) { if (arg[i] != NULL) { double *pValue = (double *)arg[i]; sum += *pValue; } else break; } printf("%f\n", sum); } break; default: printf("unknowned type!\n"); break; } } void main() { unsigned long paramType = ptChar; char a = 1, b = 2, c = 3; printSum(paramType, &a, &b, &c); paramType = ptInt; int ia = 1, ib = 2, ic = 3; printSum(paramType, &ia, &ib, &ic); paramType = ptFloat; float fa = 1, fb = 2, fc = 3; printSum(paramType, &fa, &fb, &fc); paramType = ptDouble; double da = 1, db = 2, dc = 3; printSum(paramType, &da, &db, &dc); }