注:以下内容学习于韦东山老师arm裸机第一期视频教程
一.变参函数分析
我们利用printf函数来搞懂变参函数,我们知道printf函数的原型为
int printf(const char *format, ...);
其中...代表了变参,我们通过printf函数入手来确定固定参数,可变参数,最后引出变参函数。
1.1 确定固定参数参数
固定参数是一个char* 的指针,我们可以将它所指的内容直接打印出来
#include <stdio.h>
int MyPrintf(const char *format, ...)
{
printf ("Arg1 = %s\n", format);
return 0;
}
int main(int argc, char **argv)
{
MyPrintf("format");
return 0;
}
首先利用上面的代码将固定参数打印出来,运行结果可以看到运行结果如下
1.2 手工确定可变参数
确定可变参数主要根据函数传递参数时,参数从左到右依次入栈。
可变参数会放在format这个指针变量的后面,因此我们定义一个指针来指向固定参数,然后移动指针指向可变参数,将指针指向的值打印出来
#include <stdio.h>
int MyPrintf(const char *format, ...)
{
int iArg2;
char *p = (char *)&format;
printf ("Arg1 = %s\n", format);
p = p + sizeof (char *);
iArg2 = (*(int *)p);
printf ("Arg2 = %d\n", iArg2);
return 0;
}
int main(int argc, char **argv)
{
//MyPrintf("format");
MyPrintf("format", 123);
return 0;
}
运行结果如下,成功的将可变参数打印出来了
1.3 继续修改代码,增加不同类型的可变参数
#include <stdio.h>
int MyPrintf(const char *format, ...)
{
int iArg2, iArg4;
char cArg3;
char *p = (char *)&format;
printf ("Arg1 = %s\n", format);
p = p + sizeof (char *);
iArg2 = (*(int *)p);
printf ("Arg2 = %d\n", iArg2);
p = p + sizeof (int);
cArg3 = (*(char *)p);
printf ("Arg23 = %c\n", cArg3);
p = p + sizeof (char);
iArg4 = (*(int *)p);
printf ("Arg4 = %d\n", iArg4);
return 0;
}
int main(int argc, char **argv)
{
//MyPrintf("format");
//MyPrintf("format", 123);
MyPrintf("format", 123, 'A', 456);
return 0;
}
我们添加了字符‘A’和后面的一个整数456,运行结果如下并没有打印出我们期望的456
这是因为需要4字节对齐,我们在输出字符之后移动指针的时候移动了一个字节,但是其实'A'字符后面的三个字节是空的,为了4字节对齐,因此需要修改代码如下, +3 & ~3 即可4字节对齐,对于1/2/4字节都可,有兴趣可以去验证一下。
1.4 继续修改代码
#include <stdio.h>
int MyPrintf(const char *format, ...)
{
int iArg2, iArg4;
char cArg3;
double dArg5;
char *p = (char *)&format;
printf ("Arg1 = %s\n", format);
p = p + sizeof (char *);
iArg2 = (*(int *)p);
printf ("Arg2 = %d\n", iArg2);
p = p + sizeof (int);
cArg3 = (*(char *)p);
printf ("Arg23 = %c\n", cArg3);
p = p + (sizeof (char) + 3 & ~3);
iArg4 = (*(int *)p);
printf ("Arg4 = %d\n", iArg4);
p = p + sizeof (int);
dArg5 = (*(double *)p);
printf ("dArg5 = %f\n", dArg5);
return 0;
}
int main(int argc, char **argv)
{
//MyPrintf("format");
//MyPrintf("format", 123);
//MyPrintf("format", 123, 'A', 456);
MyPrintf("format", 123, 'A', 456, 2.33);
return 0;
}
添加一个小数,运行结果如下,能够成打印出来,
但是如果将代码中的double修改为float得到的结果如下
这说明小数在printf里面是以double类型存放的。
二.自动确定可变参数
根据上面手工确定可变参数我们可以确定几个步骤
2.1 首先将指针指向第一个可变参数
2.2 a.取值
b.指针移动到下一个可变参数的地方
2.3 重复a,b
2.4 最后将指针清为NULL,避免野指针的出现
因此,可变参数就主要有了变参函数
va_list ap /* 就等价于char *ap,定义一个指针 */
va_start(ap, format) /* 就是将指针ap移动到第一个可变参数的地方,即ap = ap + sizeif(char *) */
va_arg(ap, 变量类型) /* 返回当前ap指针指向的值,然后指向下一个可变参数 */
va_end /* 将指针请为NULL */
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ap += _INTSIZEOF(t), *(*t)(ap - _INTSIZEOF(t))
#define va_end(ap) ( ap = (va_list)0 )
可以看到va_list就是char *, _INTSIZEOF(n)就是将地址4字节对齐,就是 + 3 & ~3
其中va_arg可能比较难理解,我们重新定义一下va_arg
由于va_arg要实现两个功能,
1.取值 2. 移动指针 因此我们考虑用逗号表达式来实现
表达式1,表达式2 逗号表达式依次执行表达式1,2,然后将表达式2的值作为结果
因此 表达式1要移动指针到下一个变参 ap += _INTSIZEOF(t)
表达式2要取指针的值,但是指针已经移动到下一个变参了,因此我们需要先移动回来再取值
*(*t)(ap - _INTSIZEOF(t))
因此va_arg可以定义为 ap += _INTSIZEOF(t), *(t*)(ap - _INTSIZEOF(t)))
利用这几个变参函数实现同样的效果:
#include <stdio.h>
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
/*#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))*/
#define va_arg(ap,t) (ap = ap + _INTSIZEOF(t),*(t*)(ap - _INTSIZEOF(t)))
#define va_end(ap) ( ap = (va_list)0 )
int MyPrintf(const char *format, ...)
{
int iArg2, iArg4;
char cArg3;
double dArg5;
//char *p = (char *)&format;
va_list p;
printf ("Arg1 = %s\n", format);
//p = p + sizeof (char *);
//iArg2 = (*(int *)p);
va_start(p, format);
iArg2 = va_arg(p, int);
printf ("Arg2 = %d\n", iArg2);
//p = p + sizeof (int);
cArg3 = va_arg(p, char);
printf ("Arg3 = %c\n", cArg3);
//p = p + (sizeof (char) + 3 & ~3);
iArg4 = va_arg(p, int);
//iArg4 = (*(int *)p);
printf ("Arg4 = %d\n", iArg4);
//p = p + sizeof (int);
dArg5 = va_arg(p, double);
//dArg5 = (*(double *)p);
printf ("dArg5 = %f\n", dArg5);
va_end(p);
return 0;
}
int main(int argc, char **argv)
{
//MyPrintf("format");
//MyPrintf("format", 123);
//MyPrintf("format", 123, 'A', 456);
MyPrintf("format", 123, 'A', 456, 2.33);
return 0;
}