设为首页 加入收藏

TOP

C语言中可变参数的用法(一)
2014-11-24 00:33:16 来源: 作者: 【 】 浏览:48
Tags:言中 可变 参数 用法

我们在C语言编程中会遇到一些参数个数可变的函数,例如printf():
int printf( const char *format, ...);

它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的。例如:

  printf("%d",i);

  printf("%s",s);

  printf("the number is %d ,string is:%s", I, s);

究竟如何写可变参数的C函数?以及这些可变参数的函数,编译器是如何实现的呢?

本文就这个问题进行一些探讨,希望能对大家有些帮助。会C++的朋友知道这些问题在C++里不存在,因为C++具有多态性。但C++是C的一个超集,以下的技术也可以用于C++的程序中。

(一)写一个简单的可变参数的C函数
下面我们来探讨如何写一个简单的可变参数的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 );

va在这里是variable-argument(可变参数)的意思。这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件。

下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的。函数只是打印这两个参数的值。

  void simple_va_fun(int I, …)

  {

    va_list arg_ptr;

    int j=0;

    va_start(arg_ptr, i);

    j = va_arg( arg_ptr, int );

    va_end(arg_ptr);

    printf("%d %d\n", I, j);

    return;

  }

我们可以在我们的头文件中这样声明我们的函数:

  extern void simple_va_fun(int I, …);

我们在程序中可以这样调用:

  simple_va_fun(100);

  simple_va_fun(100,200);

从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:

首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针。
然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
然后用va_arg返回可变的参数,并赋值给整数j;va_arg的第二个参数是你要返回的参数的类型,这里是int型。
最后用va_end宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的。依次调用va_arg获取各个参数。
如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:


simple_va_fun(100); 结果是:100 -123456789(会变的值)
simple_va_fun(100,200); 结果是:100 200
simple_va_fun(100,200,300); 结果是:100 200

我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有冲突。
下面一节我们探讨出现这些结果的原因和可变参数在编译器中是如何处理的。
(二)可变参数在编译器中的处理
我们知道va_start, va_arg, va_end是在stdarg.h中被定义成宏的,由于硬件平台和编译器的不同,定义的宏也有所不同。
下面以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个参数(最后一个固定参数)  
低地址 ----------------------------- <--&v
图( 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个参数(最后一个固定参数)  
低地址 ----------------------------- <--&v
图( 2 )

最后要说的是va_end宏的意思。X86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样。有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的。

在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型。

关于va_start, va_arg, va_end的描述就是这些了。我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的。

 

(三)可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Beej’s Guide Network to Progra.. 下一篇模板应用(1) 浅析API的设计

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: