C变参的实现

原帖:http://blog.csdn.net/xlliu0226/archive/2008/03/31/2233529.aspx

 

C 语言中,函数参数的 传 递方式有值传和址传 . 值传是把实参的一个专用的、临时的复制值给被调函数中相应的形参被调用函数使用、修改这个传来的复制 值,不会影响实参的值 . 址传则 是把变量 ( 实参 ) 的地址传给被调 函数 . 被调函数通过这个地址找到该变量的存放位置,直接对该地址中存放的变量的内容进行存取操作 . 因此,在被调用函 数中可 以修改实参的值 . 这也是函数参数址 传的优点 . 无论是值传还是址传,都要求实参的数目及类型与形参要完全一致 . 在一般的程 序设计语言中,函数参数的数目及类型是不可变的 . 即函数被设计之 后,只能接收已固定个数和固定类型的实参 . 这样在编译时,函数形参的存储空间便于确定 . 是在 C 语言中,不但参数的类型可变,参数的个数也是可变的 . 也就是说,在形参 表中可以不明确指定传递参数的个数和类型,一个常见的库函数 Printf() 就是如此 . 这种函数称之为可 变长参数函数 ( 变参函数 ). 可变长参数函数的参数数目和类型虽然是可变,但其设计原理与固定参数函数的设计原理是一致的,必须 有办法告诉变参函数没有指定的参数的个数和类型。下面我们通过对可变长参数函数的理解和设计,在教 学中更有助于加深掌握 C 语言函数设计的思想方法 . 利用其 它语言所不具有的这一可变长参数功能,可以开发灵活、方便、简洁、功能强的程序模块 . 1 可变长参数函数 的设计方法
  在标准文件 stdarg.h 中包含带参数的宏定义

typedef  void   * va_list
  
#define  va_arg(ap,type) (*((type *)(ap))++)
  
#define  va_start(ap,lastfix) (ap=…)
  
#define  va_end(ap)

(1) 可变长参数函数用规定格式定义为 类型函数名 (firstfix,…,lastfix,…)”.firstfix,…,lastfix 表示函数参数列表中的第一个和最后一个固定参数,该参数列表中至少要有一个固定参数,其作用是为了 给变参函数确定列表中参数的个数和参数的类型 .

(2)
指针类型 va _ list 用来说明一个变量 ap(argument pointer—— 可变参数指针 ) ,此变量将依次引 用可变参数列表中用省略号 “…” 代替的每一个参数 . 即指向将要操作的变参 .

(3)
va_start (ap,lastfix) 是为了初始化变参指针 ap, 以指向可变参数列表中未命名的第一个参数,即指向 lastfix 后的 第一个变参 . 它必须在指针使用之前调用一次该宏,参数列表中至少有一个未命名的可变参数 . 从宏定义可知其 正确性 .

(4)
va _ arg (ap,type) 调用,将 ap 指向下一个可变 参数,而 ap 的类型由 type 确定, type 数据类型不使用 float 类型 . 调用后将新的变参可指向一个工作变参,如 iap=va _ start (ap,int) 调用 .

(5)
va _ end (ap) stdarg.h 中 看出定义为空,即未定义 . 其功能完成清除变量 ap 的作用,表明程序以后不再使用,若该指针变量需再使用,必须重新调用宏 va _ start 以启 动该变量 .
2 ,应 用举例
  利用上面讨论的一般可变长参数函数的设计方法,通过实例逐步分析其特点,以加深函数实参与形参一 致性的理解 .
2.1
 变参类型相同的函数
#include  < stdio.h >
#include 
< stdarg.h >

int  mul( int  num, int  data1, )
{
    
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, )
{
  
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 ;
}

    在该例中, for{…} 循环中 的 ap 指向的下一个变参类型皆为整型,所以变参类型相同,但变参个数不定 .

2.2 可变参数在编译器中的处理  

    我们知道 va_start,va_arg,va_end 是在 stdarg.h 中被定义成宏的 , 由于 1) 硬件平台的不同 2) 编译器的不同 , 所以定义的宏也 有所不同 , 下面以 VC++ stdarg.h x86 平台的宏定义摘录如下 (’"’ 号表示折行 ):
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_end(ap) ( ap = (va_list)0 ) 
    定义 _INTSIZEOF(n) 主要是为了某些需要内存的对 齐的系统 .C 语言的函数是从右向左压入堆栈的 , (1) 是函数的参数 在堆栈中的分布位置 . 我们看到 va_list 被定义成 char*, 有一些平台或操作系统定义为 void*. 再看 va_start 的 定义 , 定义为 &v+_INTSIZEOF(v), &v 是固定参数在堆栈的地址 , 所以我们运行 va_start(ap, v) 以后 ,ap 指向第一个可变参数在堆栈的地址 , 如图 :
高地址 |-----------------------------|  
| 函数返回地址  |  
|-----------------------------|  
| |  
|-----------------------------|  
| 第n个参数(第一个可变参数)  |  
|-----------------------------|<-- va_start后ap指向 
| 第n - 1个参数(最后一个固定参数) |  
低地址
|-----------------------------|<--   &
图( 
1  ) 
    然后 , 我们用 va_arg() 取 得类型 t 的可变参数值 , 以上例为 int 型为例 , 我们看一下 va_arg int 型的返回值 : j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先 ap+=sizeof(int), 已经指向下一个参数的地址了 . 然后返回 ap-sizeof(int) int* 指针 , 这正是第一个可变参数在堆栈里的地址 ( 2). 然后用 * 取得这个地址的 内容 ( 参数值 ) 赋给 j.
高地址 |-----------------------------|  
| 函数返回地址  |  
|-----------------------------|  
| |  
|-----------------------------|<-- va_arg后ap指向 
| 第n个参数(第一个可变参数)  |  
|-----------------------------|<-- va_start后ap指向 
| 第n - 1个参数(最后一个固定参数) |  
低地址
|-----------------------------|<--   &
图( 
2  ) 

    最后要说的是 va_end 宏的意 思 ,x86 平台定义为 ap=(char*)0; 使 ap 不再指向堆栈 , 而是跟 NULL 一样 . 有些直接定义为 ((void*)0), 这样编译器不会为 va_end 产生代码 , 例如 gcc linux x86 平台就是这样定义的 . 在这里大家要注意一个问题 : 由于参数的地址用于 va_start , 所以参数不能声 明为寄存器变量或作为函数或数组类型 . 关于 va_start, va_arg, va_end 的描述就是这些了 , 我们要注意的是 不同的操作系统和硬件平台的定义有些不同 , 但原理却是相似的 .
#include  " stdio.h "
#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 ;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值