c语言可变参数宏
① c语言中可变参数宏的va_start(ap, v)
我把你的提问分为3个问题:
1、为什么printf("%s", ap);输出不了?
2、va_start(ap, v)的定义中为什么使用二级指针?
3、va_arg(ap,t) 的定义中为什么用*(t *),它的作用是?
在解释之前,先确认一个小问题:
在C语言中,指针这种类型的大小实际上一样的,我的意思是说无论是char *a,还是int *a,或者是char **a,a这个指针变量所占用的内存空间是一样的(都是sizeof(a),究竟是等于4,还是8取决于CPU的位数)
先回答第一个问题:
你应该知道va_list的定义:typedef char * va_list;
也就是说ap可以理解为一个char *类型的变量,va_start(ap,c)这个执行之后,ap确实指向了可变参数列表中的第一个参数,注意【是ap这个指针指向了第一个参数】,而如果你的第一个参数是一个字符串(C语言中也就意味着是一个char*的变量),这样的话,ap这个指针就指向了一个char*类型的指针变量,【指向指针的指针变量是二级指针变量】这个我就不用多说了吧,所以printf("%s", ap);是无法输出的,而修改为printf("%s", *(char **)ap);应该就可以输出了!
然后是第二个问题:
这里先说一下函数调用过程中参数传递的问题:
【 函数参数是以数据结构:栈的形式存取,从右至左入栈。
首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, char *y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 char *y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。】
注意,x,y,z这几个变量是存放到堆栈中的,所以我们需要获得的不是y这个变量本身,而是它在堆栈中的地址,而ap这个指针变量就是保存着堆栈中函数入参的地址的,所以在va_start(ap, v)的定义中要使用&v,而不管v变量本身是什么类型的(哪怕v是一个指针变量,甚至是二级指针)&v都表示一个地址,所以可以强制转换为va_list类型(也就是char *)。
第三个问题:
要睡觉了,先自己想吧,如果还不明白,就留言追问吧。
② c语言如何封装一个带有可变参数的方法
需要借用C语言的VA_LIST宏定义,及相关操作来实现可变参数。
VA_LIST所在头文件:#include <stdarg.h>,用法如下:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。
以下是一个自定义打印接口的实现:
intmy_printf(constchar*fmt,...)//...表示参数可变
{
va_listargs;//定义va_list
staticchargc_PrintfOutBuff[1000];
va_start(args,fmt);//初始化
vsnprintf((char*)gc_PrintfOutBuff,1000,(char*)fmt,args);//这里没有使用VA_ARG取回单个变量,而是借用vsnprinf一次性读取。
va_end(args);//结束获取
puts("%s",(constchar*)gc_PrintfOutBuff);//使用。
return0;
}
③ C语言头文件ansidecl.h中定义的宏VA_OPEN和VA_FIXEDARG和VA_CLOSE表示什么
这个是用于处理可变参数的,其实C标准只定义了
va_start
,
va_end
,
va_
,
va_arg
这几个宏,而
va_list
是一个存储可变参数信息的对象。
va_start
用于初始化可变参数列表
va_
将参数列表拷贝一份,而不直接使用源参数列表,当然,这个拷贝参数列表中的参数信息和源列表是一样的。
var_arg
抽取参数列表中的下一个参数
var_end
用于结束参数处理(如果函数调用了va_start,在函数返回之前应该调用va_end结束本次处理)。
egg.
void
printInt(int
num,
...)
{
va_list
ap;//用于存储可变参数的信息的列表
var_start(ap,num);//初始化参数列表,你要告诉var_start,最后一个已命名的参数是哪一个(其实就是
...
前面的那个),这里就是num,因为va_start内部要找到可变参数的首地址,所以要知道从哪个地址开始是可变参数的地址,这里传递num,也就是从地址&num+1开始作为可变参数的地址。可变参数信息会存在ap这个list中
for(int
i=0;i<num;i++)
{
int
val=var_arg(ap,int);//使用va_arg宏从参数列表ap中取出一个参数,由于宏并不知道我们传递的参数的类型,所以它无法返回,所以你要告诉它参数是什么类型的,然后它就返回一个这种类型的参数值给你,参数列表内部维护一个指针,用于指示当前处理到哪个地址,调用va_arg后指针会移动到下一个参数的位置,那么它怎么知道下一个参数在哪里?你告诉它参数类型是什么,它就会向后移动这个参数类型所占的字节数,例如你从里面拿了一个int,那么它就+4,又从里面拿了一个char,它就+1。每次调用完va_arg后,指针都是指向下一个待处理的参数的地址。
printf("The
value
is:
%d
",val);
}
va_end(ap);//函数返回前,记得调用va_end这个宏来结束参数的处理,这个很重要,不要忘记。
}
printInt(3,24,36,71);//
调用printInt函数
④ C语言 可变参数宏的问题
这个问题可以这样考虑:
你在write_log()函数里调用了vfprintf()函数,其实这个vfprintf()就是一个可以接受你从上层函数传下来的可变参数串的函数。
你现在要在
log_info()
函数下调用
write_log()
函数,并想把可变参数串传给它,你只要参考
vfprintf()
的函数定义来定义
write_log()
函数就可以。
c语言中
vfprintf()
函数的定义是:
int
vfprintf(file
*stream,
const
char
*format,
va_list
ap);
不知你是否能受到启发。
⑤ C语言可变参数传递的问题
方案是有的,但是需要用到汇编代码。而且,不同的CPU架构,代码写起来还不一样。
大概的方法,通过解析fmt的内容,找出其中的%d、%u等格式控制符,根据格式控制符,提取出后面的各个参数。参数如何提取,需要用到汇编代码,而且不同的CPU架构,代码实现是有差异的。
不过,在下觉得,您可能并不需要真正实现这样的函数。或许将fun1定义为类似如下的一个宏,就能解决你的问题了吧。
#define fun1(a,b,fmt, args...) \
do \
{ \
if (a>b) \
{ \
fun2(fmt, ##args); \
} \
else \
{ \
fun2(fmt, ##args); \
} \
} while (0)