注:以下内容学习于韦东山老师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; }