c语言中的不定参数

在采用c语言编程时,函数中的形式参数数目通常是确定的,在调用的时候要依次给出与形式参数对应的所有实际参数,但在某些情况下希望函数的参数个数可以根据需要确定,如printf,scanf函数等,c编译器提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性,这些宏包括va_start,va_arg和va_end等。
在采用ANSI标准形式的时候,参数个数可变的函数原型声明为:
type funcname(type para1,...);
这种形式至少需要一个固定的形式参数,后面的省略号是函数原形的一部分,type是函数返回值和形式参数的类型;
若采用UNIX System V兼容的声明方式时,参数个数可变的函数原型为:
type funcname(va_list);
va_dcl
这种形式不需要提供固定参数,type是函数返回值得类型,va_dcl是对函数原型声明中的ca_alist的详细声明,实际是一个宏定义,对不同硬件平台采用不同的类型来定义,但在最后都包括了一个分号,因此va_dcl后不需要再加分号了,va_dcl在代码中必须原样给出,va_alist在vc中可原样给出也可以略去,但在UNIX上的cc或Linux上的gcc中都要省略掉;
由于硬件平台的不同和编译器的不同,所以定义的宏也有所区别,下面看一下vc++6.0下这些宏的定义以及使用和说明:vc下这些宏的定义都在stdarg.h头文件中


 

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_end(ap) (ap = (va_list))



va在这里是variable-argument(可变参数)的意思.

 

(1)函数使用的时候会首先定义一个va_list型的变量,这个变量是存储参数地址的指针,因为得到参数地址之后,再结合参数类型,才能得到参数的值,va_list被定义为char*,因为目前我们使用的PC机上,字符指针类型用来存储内存单元地址,有些机器上也会定义为void*类型;

(2)定义_INTSIZEOF(n)主要是为了某些需要内存对齐的系统,这个宏的目的就是为了得到最后一个固定参数的实际内存大小;一般说来它和sizeof一样;

(3)

va_start(va_list,type)的作用是指向第一个可变参数,va_start的定义为&v + _INTSIZEOF(v),这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址;

(4)va_arg(va_list,type)的作用是根据指定的的参数类型取得本参数的值,并使va_list指向像一个参数的起始地址,相当于进栈操作,

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

  这个宏做了两个事情,

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

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

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

函数体内可以多次遍历这些参数,但是都必须以vastart开始,并以vaend结尾。

 

 

va_arg()整型只能用int表示,不能用short否则编译会有警告,执行会出错;

                              浮点数只能用double,不能用float,否则结果同上;

                                指针类型可以使用;

 

使用可变参数要注意的问题:

1.标准c库中的三个宏作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道实际参数的数目的;

   ①在固定参数中设置标志——printf函数就是用第一个固定字符串参数确定的;

   ②在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾;

 无论什么方法,程序员都应该在文档中告诉调用者自己约定;

2.实现可变参数的要点是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:函数栈的生长方向,参数的入栈顺序,cpu对齐方式,内存地址表达方式;va_list的实现方式是由内存地址的表达方式决定的,_INTSIZEOF(n)的引入规则是由cpu对齐方式决定的,它和第一个和第二个一起决定了va_start的实现;

3.取得地址后,再结合参数类型,就可正确处理参数;

参考资料:

the c programming language

http://blog.163.com/fenglanghai@126/blog/static/33568779201192214147570/















你可能感兴趣的:(c语言)