#include < stdio.h >
#include < stdarg.h >
int mul( int num, int data1,
![dot.gif](https://i-blog.csdnimg.cn/blog_migrate/918e8df969f9f8c8d002f25cda86cade.gif)
{
int total = data1;
int arg,i;
va_list ap;
va_start(ap,data1);
for (i = 1 ;i < num;i ++ )
{
arg = va_arg(ap, int );
total *= arg;
}
va_end(ap);
return total;
}
long mul2( int i,
![dot.gif](https://i-blog.csdnimg.cn/blog_migrate/918e8df969f9f8c8d002f25cda86cade.gif)
{
int * p,j;
p = & i + 1 ; // p指向参数列表下一个位置
long s = * p;
for (j = 1 ;j < i;j ++ )
s *= p[j];
return s;
}
int main()
{
printf( " %d\n " ,mul( 3 , 2 , 3 , 5 ));
printf( " %d\n " ,mul2( 3 , 2 , 3 , 5 ));
return 0 ;
}
printf的设计
#include " stdlib.h "
#include < stdarg.h >
void myprintf( char * fmt, ) // 一个简单的类似于printf的实现, // 参数必须都是int 类型
{
// char* pArg=NULL; // 等价于原来的va_list
va_list pArg;
char c;
// pArg = (char*) &fmt; // 注意不要写成p = fmt !!因为这里要对参数取址,而不是取值
// pArg += sizeof(fmt); // 等价于原来的va_start
va_start(pArg,fmt);
do
{
c =* fmt;
if (c != ' % ' )
{
putchar(c); // 照原样输出字符
}
else
{ // 按格式字符输出数据
switch ( *++ fmt)
{
case ' d ' :
printf( " %d " , * (( int * )pArg));
break ;
case ' x ' :
printf( " %#x " , * (( int * )pArg));
break ;
case ' f ' :
printf( " %f " , * (( float * )pArg));
default :
break ;
}
// pArg += sizeof(int); // 等价于原来的va_arg
va_arg(pArg, int );
}
++ fmt;
} while ( * fmt != ' \0 ' );
// pArg = NULL; // 等价于va_end
va_end(pArg);
return ;
}
int main( int argc, char * argv[])
{
int i = 1234 ;
int j = 5678 ;
myprintf( " the first test:i=%d " ,i,j);
myprintf( " the secend test:i=%f; %x;j=%d; " ,i, 0xabcd ,j);
system( " pause " );
return 0 ;
}
可变参数在编译器中的处理
我们知道 va_start,va_arg,va_end 是在 stdarg.h 中被定义成宏的 , 由于 1) 硬件平台的不同 2) 编译器的不同 , 所以定义的宏也有所不同 , 下面以 VC++ 中 stdarg.h 里 x86 平台的宏定义摘录如下 (’"’ 号表示折行 ):
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/dot.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/dot.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.关于va_start, va_arg, va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的