由于在C语言中没有函数重载,解决不定数目函数参数的问题变得比较麻烦,即使采用C++,如果参数个数不确定,也很难采用函数重载。
所使用的宏:
Void va_start(va_list arg_ptr, prev_param);
Type va_arg(va_list arg_ptr, type);
Void va_end(va_list, arg_ptr);
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 += _INSIZEOF(t)-_INTSIZEOF(t)) //下一个参数地址
#define va_end(ap) (ap = (va_list)0) //将指针置为无效
参数在堆栈中的分布:
在进程中,堆栈地址是从高到低分配,当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的。
参数在堆栈中的分布情况:
最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段
解释_INTSIZEOF(n) (sizeof(n) + sizeof(int)) & ~(sizeof(int) - 1):
~是位取反的意思。
_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8.~(sizeof(int) - 1)就应该为~(4 -1)=~(00000011b)=11111100b,这样任何数&~(sizeof(int)-1)后最后两位肯定为0,就肯定是4的整数倍了。(sizeof(n) + sizeof(int) - 1)就是将大于4m但是小于等于4(m + 1)的数提高到大于等于4(m +1),但小于4(m + 2),这再&(sizeof(int) - 1)后就正好将原长度补齐到4的倍数。
Va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
这个宏做了两件事,1.用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值;2.计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。
Va_end:x86平台定义为ap=(char*)0,使ap不再指向堆栈,而是跟null一样,有些直接定义为((void*)0),这样不会为va_end产生代码,