C语言可变参数研究

一. 何谓可变参数

int printf( const char* format, ...);

这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用”…”表示). 而我们又可以用各种方式来调用printf,如:

printf("%d",value);

printf("%s",str);

printf("the number is %d ,stringis:%s", value, str);

 

二.实现原理

C语言用宏来处理这些可变参数。这些宏看起来很复杂,其实原理挺简单,就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址。下面我们来分析这些宏。在VC中的stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:

 

typedef char *va_list;

/*把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的*/

#define _INTSIZEOF(n) ( (sizeof(n) +sizeof(int) - 1) & ~(sizeof(int) - 1) )

/*_INTSIZEOF (n)宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof(int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果sizeof(n)在5-8之间,那么 _INTSIZEOF(n)=8。*/

#define va_start(ap,v)( ap =(va_list)&v + _INTSIZEOF(v) )

/*va_start 的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start (ap, v)以后,ap指向第一个可变参数在的内存地址*/

#define va_arg(ap,t) ( *(t *)((ap +=_INTSIZEOF(t)) - _INTSIZEOF(t)) )

/*这个宏做了两个事情,

①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值

②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。*/

  #defineva_end(ap) ( ap = (va_list)0 )

/*x86 平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. */

 

以下再用图来表示:

 

在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。

|——————————————————————————|

|最后一个可变参数 | ->高内存地址处

|——————————————————————————|

...................

|——————————————————————————|

|第N个可变参数 |->va_arg(arg_ptr,int)后arg_ptr所指的地方,

| | 即第N个可变参数的地址。

|——————————————— |

………………………….

|——————————————————————————|

|第一个可变参数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方

| | 即第一个可变参数的地址

|——————————————— |

|———————————————————————— ——|

| |

|最后一个固定参数 | -> start的起始地址

|—————————————— —| .................

|—————————————————————————— |

| |

|——————————————— |-> 低内存地址处

 

 

三.printf研究

 

下面是一个简单的printf函数的实现,

#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型
{
 char* pArg=NULL; //等价于原来的va_list
 charc;
 
 pArg= (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值
 pArg+= sizeof(fmt); //等价于原来的va_start
 
 do
 {
  c=*fmt;
  if(c != '%')
  {
  putchar(c); //照原样输出字符
  }
 else
  {
   //按格式字符输出数据
  switch(*++fmt)
   {
  case 'd':
   printf("%d",*((int*)pArg));
   break;
  case 'x':
   printf("%#x",*((int*)pArg));
   break;
  default:
   break;
   }
  pArg += sizeof(int); //等价于原来的va_arg
  }
 ++fmt;
 }while (*fmt != '\0');
 pArg= NULL; //等价于va_end
 return;
}
 
int main(int argc, char* argv[])
{
 inti = 1234;
 intj = 5678;
 
 myprintf("the first test:i=%d",i,j);
 myprintf("the secend test:i=%d;%x;j=%d;",i,0xabcd,j);
 system("pause");
 return 0;
}

 

在intel+win2k+vc6的机器执行结果如下:

the first test:i=1234

the secend test:i=1234; 0xabcd;j=5678;

 

四.应用

求最大值:

#include //不定数目参数需要的宏
int max(int n,int num,...)
{
 va_list x;//说明变量x
 va_start(x,num);//x被初始化为指向num后的第一个参数
 intm=num;
 for(int i=1;i {
  //将变量x所指向的int类型的值赋给y,同时使x指向下一个参数
  inty=va_arg(x,int);
 if(y>m)m=y;
  }
 va_end(x);//清除变量x
 return m;
}
 
main()
{
printf("%d,%d",max(3,5,56),max(6,0,4,32,45,533));
}


阅读更多
个人分类: C/C++编程
想对作者说点什么? 我来说一句

C语言可变参数

2011年09月13日 207KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭