c语⾔⼦程序返回多个参数,C语⾔中实现参数个数不确定的函
C语⾔中有⼀种长度不确定的参数,形如:"…",它主要⽤在参数个数不确定的函数中,我们最容易想到的例⼦是printf函数。(注意:在
C++中有函数重载(overload)可以⽤来区别不同函数参数的调⽤,但它还是不能表⽰任意数量的函数参数。)
C语⾔⽤va_start等宏来处理这些可变参数。这些宏看起来很复杂,其实原理挺简单,就是根据参数⼊栈的特点从最靠近第⼀个可变参数的固定参数开始,依次获取每个可变参数的地址。 在标准C语⾔中定义了⼀个头⽂件专门⽤来对付可变参数列表,它包含了⼀组宏,和⼀个va_list的typedef声明。针对不同平台有不同的宏定义,我们选取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 )
//或者简化为 typedef char* va_list; #define va_start(list) list = (char*)&va_alist #define va_end(list) #defin
e va_arg(list, mode)\ ((mode*) (list += sizeof(mode)))[-1]
其中说明如下:
_INTSIZEOF(n)宏是为了考虑那些内存地址需要对齐的系统.
为了能从固定参数依次得到每个可变参数,va_start,va_arg充分利⽤下⾯两点: 1. C语⾔在函数调⽤时,先将最后⼀个参数压⼊栈 2.X86平台下的内存分配顺序是从⾼地址内存到低地址内存 ⾼位地址 第N个可变参数 第N-1个可变参数 ...... 第⼆个可变参数 第⼀个可变参数ap 固定参数 v 低位地址
由上图可见,&v是固定参数在内存中的地址,在调⽤va_start后,ap指向第⼀个可变参数。这个宏的作⽤就是在v的内存地址上增加v所占的内存⼤⼩,这样就得到了第⼀个可变参数的地址。 接下来,可以这样设想,如果我能确定这个可变参数的类型,那么我就知道了它占⽤了多少内存,依葫芦画瓢,我就能得到下⼀个可变参数的地址。 让我们再来看看va_arg,它先ap指向下⼀个可变参数,然后减去当前可变参数的⼤⼩即得到当前可变参数的内存地址,再做个类型转换,返回它的值。 要确定每个可变参数的类型,有两种做法,要么都是默认的类型,要么就在固定参数中包含⾜够的信息让程序可以确定每个可变参数的类型。⽐如,printf,程序通过分析format字符串就可以确定每个可变参数⼤类型。
最后⼀个宏就简单了,va_end使得ap不再指向有效的内存地址。 其实在varargs.h头⽂件中定义了UNIX
System V实⾏的va系列宏,⽽上⾯在stdarg.h头⽂件中定义的是ANSI C形式的宏,这两种宏是不兼容的,⼀般说来,我们应该使⽤ANSI C形式的va宏。
从⽽总的使⽤原则是: 1)⾸先在函数⾥定义⼀个va_list型的变量,这⾥是arg_ptr,这个变量是指向参数的指针. 2)然后⽤va_start宏初始化变量arg_ptr,这个宏的第⼆个参数是第⼀个可变参数的前⼀个参数,是⼀个固定的参数. 3)然后⽤va_arg返回可变的参数,并赋值给整数j. va_arg 的第⼆个参数是你要返回的参数的类型,这⾥是int型. 4)最后⽤va_end宏结束可变参数的获取.然后你就可以在函数⾥使⽤第⼆个参数了.如果函数有多个可变参数的,依次调⽤va_arg获取各个参数. 5)、标准C库的中的三个宏的作⽤只是⽤来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数⽬的。 6)、在实际应⽤的代码中,程序员必须⾃⼰考虑确定参数数⽬的办法,如  a)在固定参数中设标志--  printf函数就是⽤这个办法。  b)在预先设定⼀个特殊的结束标记,就是说多输⼊⼀个可变参数,调⽤时要将最后⼀个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本⽂前⾯的代码就是采⽤这个办法. ⽆论采⽤哪种办法,程序员都应该在⽂档中告诉调⽤者⾃⼰的约定。 7)、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下⼏个因素决定:  a)函数栈的⽣长⽅向  b)参数的⼊栈顺序  c)CPU的对齐⽅式  d)内存地址的表达⽅式 8)取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。
Example: 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; } //----------------------------------------------------------------------------- 问题:运⾏时才确定的参数 有没有办法写⼀个函数,这个函数参数的具体形式可以在运⾏时才确定?
答案与分析: ⽬前没有"正规"的解决办法,不过独门偏⽅倒是有⼀个,因为有⼀个函数已经给我们做出了这⽅⾯的榜样,那就是main(),它的原型是: int main(int argc,char *argv[]); 深⼊想⼀下,"只能在运⾏时确定参数形式",也就是说你没办法从声明中看到所接受的参数,也即是参数根本就没有固定的形式。常⽤的办法是你可以通过定义⼀个void *类型的参数,⽤它来指向实际的参数区,然后在函数中根据根据需要任意解释它们的含义。这就是main函数中argv的含义,⽽argc,则⽤来表明实际的参数个数,这为我们使⽤提供了进⼀步的⽅便,当然,这个参数不是必需的。 虽然参数没有固定形式,但我们必然要在函数中解析参数的意义,因此,理所当然会有⼀个要求,就是调⽤者和被调者之间要对参数区内容的格式,⼤⼩,有效性等所有⽅⾯达成⼀致,否则南辕北辙各说各话就惨了。
问题:可变长参数中类型为函数指针 我想使⽤va_arg来提取出可变长参数中类型为函数指针的参数,结果却总是不正确,为什么?
答案与分析: 这个与va_arg的实现有关。⼀个简单的、演⽰版的va_arg实现如下: #define va_arg(argp, type) \ (*(type *)(((argp) += sizeof(type)) - sizeof(type))) 其中,argp的类型是char *。 如果
你想⽤va_arg从可变参数列表中提取出函数指针类型的参数,例如 int (*)(),则va_arg(argp, int (*)())被扩展为: (*(int (*)() *)(((argp) += sizeof (int (*)())) -sizeof (int (*)()))) 显然,(int (*)() *)是⽆意义的。 解决这个问题的办法是将函数指针⽤typedef定义成⼀个独⽴的数据类型,例如: typedef int (*funcptr)(); 这时候再调⽤
va_arg(argp, funcptr)将被扩展为: (* (funcptr *)(((argp) += sizeof (funcptr)) - sizeof (funcptr))) 这样就可以通过编译检查了。
问题:可变长参数的获取 有这样⼀个具有可变长参数的函数,其中有下列代码⽤来获取类型为float的实参: va_arg (argp, float); 这样做可以吗?  答案与分析: 不可以。在可变长参数中,应⽤的是"加宽"原则。也就是float类型被扩展成double;char, short被扩展成int。因此,如果你要去可变长参数列表中原来为float类型的参数,需要⽤va_arg(argp, double)。对char和short类型的则⽤va_arg(argp, int)。固定的参数⼀定要放前⾯!! int FUNC(int xx,...) { int tmp; va_list arg_ptr; // ⽤va_list声明指向引数列表的 Pointer
va_start(arg_ptr,xx); // 从引数表中取得第⼀个引数 // 第⼀个引数就是 xx ⾃⼰(xx是最后⼀个固定变量)
tmp=va_arg(arg_ptr,int); // 从引数表中取得int 型态引数 // ⼀直呼叫 va_arg 即可取得所 // 有引数(第⼆个参数int是当前不定变量的类型 )
va_end(arg_ptr); // 取完後将 arg_ptr = NULL // 以便归还 stack 空间(要在所有的不定变量引⽤完之后再⽤va_end(),否则会有⽆法预测的结果。)
}
理论上不定参数是可以⽆限多的,但碍於 memory 及 stack ⼤⼩ 所以项数通常有上限,如 Turbo C 最多 200 个,⼀超过就出现如下 " Fatal stack overflow error-System halted ",然後当掉,你连 暖开机都不⾏;所以当你使⽤⼀套 Compiler 时⼀定要有其参考⼿册 或资料,以免搞半天除错後,还不出原因!! (P.S. 像 Turbo C 2.0 只能⽤ 4000 个 if ,....太多要注意的!!)
说了⼀堆,不知道你会⽤了没?! 最後⽤⼀个画多边型的函数做□ 例,顺便做结束。
/* EX */ #include // 记得要 include 才能⽤ #define END_P -400 // ⽤来判断是否为最後⼀个参数
void DrawPoly(int color,...) { va_list arg_ptr; int x[200],y[200]; // 最多 200 个点 int p_c=0; // 计算有⼏点 int i; // 计数器
va_list(arg_ptr,color);
while(((x[p_c]=va_arg(arg_ptr,int))>END_P) \ &&((y[p_c]=va_arg(arg_ptr,int))>END_P)\ && p_c<=200) // 不是最後⼀点且⼩於200 个点
p_c++;
if(p_c<3) return ; // 不到 3 点不成⼀多边型,So 跳出
for(i=0;i
va_end(arg_ptr); }
深⼊分析头函数: 最近在编写不定参数函数的时候发现: 在turbo c中编译可以通过但是在c-free中却不能编译,究其因,才知: 在turbo c中的stdarg.h中是这样的: typedef void *va_list;
#define va_start(ap, parmN) (ap = ...)/*在这⾥...是⼀个参数栈地址*/ #define va_arg(ap, type) (*((type *)(ap))++) #define
va_end(ap) #define _va_ptr (...) /*以上的东西应该不⽤再解释了.所以我写的程序: void *arg_ptr=...;/*tc中编译通过,但是cfree中不然,何故?*/ 原来在cfree中的stdarg.h中却是这样的: typedef char* va_list; #define __va_argsiz(t) \ (((sizeof(t) + sizeof(int) - 1) /
sizeof(int)) * sizeof(int))/*计算t类型的参数所占的栈的长度*/ #ifdef __GNUC__ #define va_start(ap, pN) \ ((ap) = ((va_list)
__builtin_next_arg(pN)))/*这个宏先不说,但是意思应该不难理解的*/ #else #define va_start(ap, pN) \ ((ap) = ((va_list) (&pN) +
__va_argsiz(pN)))/*从栈的最后⼀个确定参数位置往下的第⼀个便是不定参数的开始地址了!*/ #endif /*相应的程序变成了:*/ char* axsprintf(const char* format,...) { /*void * params=...;*/ char *params=(char*)(&format)+sizeof(format); char result[255];调用子程序的例子
查⼀下随VS⼀起安装的MSDN上边已经讲解的很详细了,params是关键字 原本每个函数所带的形式参数的数⽬是在定义函数是指定的,但是可能在某些情况下参数的数⽬是可变的,这时候可以使⽤params关键字,每个函数只能有⼀个params关键字修饰的参数,并且params关键字后边不允许存在其他参数,params关键字修饰的参数必须是⼀维数组,可以是任何类型的⼀维数组. 给你个例⼦: private void ss(string strpam1,params int param2[]) {    } 调⽤时 this.ss("参数1",2,3,4);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。