大家熟知的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);
- }
http://blog.csdn.net/rabinsong/article/details/8946514