c++ 怎么php 获取函数参数列表的参数个数

trackbacks-0
先看一个代码void myfun(int i, int ii)
cout && i && "
" && ii && endl;
void main()
int b = 3;
int arr[] = {6,7,8,9,10};
int *ptr = arr;
*(ptr++) += 123;
printf("%d\t%d\n", *ptr, *++ptr);
int i = 10;
myfun(i, ++i);
VS2008输出:
函数的调用规范
&&&&& 函数的调用规范,也称为调用约定(Calling convention)。函数的调用规范决定了函数调用时,实参压栈、退栈及堆栈释放方式,以及函数名改编(Name Mangling)的方案,也即命名规范。
&&&&& Windows环境下常用的调用规范有:
&&&& 1)__cdecl:这是C/C++函数默认的调用规范,参数从右向左依次传递,压入堆栈,由调用函数负责堆栈的清退。这种方式适用于传递个数可变的参数给被调用函数,因为只有调用函数才知道它传递了多少个参数给被调函数。如printf函数。
&&&& 2)__stdcall:参数从右向左依次传递,并压入堆栈,由被调用函数清退堆栈。当函数有可变个数参数,自动转化为__cdecl调用规范。
&&&& 3)__thiscall:这是C++非静态成员函数的默认调用规范,不能使用个数可变的参数。调用非静态成员函数的时候,this指针直接保存在ECX寄存器中,不入栈。其他方面同__stdcall。
&&&& 4)__fastcall
&&&&& 凡是接口函数都必须显示指定其调用规范,除非接口函数是类的非静态成员函数。
关于结果的解释
&&&& 据说此题是华为的笔试题。但可能出题者也没理解此种的奥秘,而只知道结果。关于函数调用的参数求值顺序,语言实现标准是未定义的。之所以出现如图所示的结果,不是由函数的调用规范决定的(从右向左对参数压栈),而是由微软编译器的具体实现:参数求值按照自右向左顺序决定的。函数调用压栈压入的是表达式的计算后的值,而不是表达式本身。
&&& 另外,如果调用myfun(i, ++i); 改为myfun(i, i++);。将输入:11&&&& 10
进一步的困惑
1: int main()
int i = 0;
printf( "%d, %d, %d, %d\n", i++, ++i, i, i++ );
&&&& 它在Visual C++ 2008的环境下编译时,输出结果为2, 3, 3, 0,在MinGW的GCC环境下编译运行时,输出结果则为2, 2, 1, 0。可见不同的编译器在实现时,对printf函数的参数进栈所作策略和抉择不同,导致了输出结果不同。结合上面关于对myfun的调用,发现即便在同一个编译器中,参数的求值顺序有时候也会不同。
&&&& 进一步学习,请参考卡
阅读(...) 评论()C/C++可变参数函数
> C/C++可变参数函数
C/C++可变参数函数
c/c++支持的,即的参数是不确定的。
一、为什么要使用的?
一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定,因此c语言引入函数。这也是c功能强大的一个方面,其它某些语言,比如fortran就没有这个功能。
典型的可变参数函数的例子有大家熟悉的printf()、scanf()等。
二、c/c++如何实现可变参数的函数?
为了支持可变参数函数,语言引入新的调用协议, 即语言调用约定 __cdecl 。 采用/语言编程的时候,默认使用这个调用约定。如果要采用其它调用约定,必须添加其它关键字声明,例如WIN32 API使用PASCAL调用约定,函数名字之前必须加__stdcall关键字。
采用C调用约定时,函数的参数是从右到左入栈,个数可变。由于函数体不能预先知道传进来的参数个数,因此采用本约定时必须由函数调用者负责堆栈清理。举个例子:
//C调用约定函数int __cdecl Add(int a, int b){return (a + b);}
函数调用:Add(1, 2);//汇编代码是:push 2 ;参数b入栈push 1 ;参数a入栈call @A调用函数。其实还有编译器用于定位函数的表达式这里把它省略了add esp,8 ;调用者负责清栈
如果调用函数的时候使用的调用协议和函数原型中声明的不一致,就会导致栈错误,这是另外一个话题,这里不再细说。
另外c/c++编译器采用宏的形式支持可变参数函数。这些宏包括va_start、va_arg和va_end等。之所以这么做,是为了增加程序的可移植性。屏蔽不同的硬件平台造成的差异。
支持可变参数函数的所有宏都定义在stdarg.h 和 varargs.h中。例如标准ANSI形式下,这些宏的定义是:
typedef char * va_ //字符串指针
#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是为了按照整数字节对齐指针,因为c调用协议下面,参数入栈都是整数字节(指针或者值)。
三、如何定义这类的函数。
可变参数函数在不同的系统下,采用不同的形式定义。
1、用ANSI标准形式时,参数个数可变的函数的原型声明是:
type funcname(type para1, type para2, …);
关于这个定义,有三点需要说明:
一般来说,这种形式至少需要一个普通的形式参数,可变参数就是通过三个’.'来定义的。所以”…”不表示省略,而是函数原型的一部分。type是函数返回值和形式参数的类型。例如:
int MyPrintf(char const* fmt, …);
但是,我们也可以这样定义函数:
void MyFunc(…);
但是,这样的话,我们就无法使用函数的参数了,因为无法通过上面所讲的宏来提取每个参数。所以除非你的函数代码中的确没有用到参数表中的任何参数,否则必须在参数表中使用至少一个普通参数。
注意,可变参数只能位于函数参数表的最后。不能这样:
void MyFunc(…, int i);
2、采用与UNIX 兼容系统下的声明方式时,参数个数可变的函数原型是:
type funcname(va_alist);
但是要求函数实现的时候,函数名字后面必须加上va_dcl。例如:
#include int average( va_list );
void main( void ){。。。//代码}
/* UNIX兼容形式*/int average( va_alist )va_dcl{。。。//代码}
这种形式不需要提供任何普通的形式参数。type是函数返回值的类型。va_dcl是对函数原型声明中参数va_alist的详细声明,实际是一个宏定义。根据平台的不同,va_dcl的定义稍有不同。
在varargs.h中,va_dcl的定义后面已经包括了一个分号。因此函数实现的时候,va_dcl后不再需要加上分号了。
3、采用头文件stdarg.h编写的程序是符合ANSI标准的,可以在各种操作系统和硬件上运行;而采用头文件varargs.h的方式仅仅是为了与以前的程序兼容,两种方式的基本原理是一致的,只是在语法形式上有一些细微的区别。 所以一般编程的时候使用stdarg.h。下面的所有例子代码都采用ANSI标准格式。
四、可变参数函数的基本使用方法
下面通过若干例子,说明如何实现可变参数函数的定义和调用。
//================================ 例子程序1 ===============#include & stdio.h &#include & string.h &#include & stdarg.h &
/* 函数原型声明,至少需要一个确定的参数,注意括号内的省略号 */int demo( char *, … );
void main( void ){demo(”DEMO”, “This”, “is”, “a”, “demo!”, “\0″);}
int demo( char *msg, … ){va_ /* 定义保存函数参数的结构 */int argno = 0; /* 纪录参数个数 */char * /* 存放取出的字符串参数 */
// 使用宏va_start, 使argp指向传入的第一个可选参数,// 注意 msg是参数表中最后一个确定的参数,并非参数表中第一个参数va_start( argp, msg );
while (1){//取出当前的参数,类型为char *//如果不给出正确的类型,将得到错误的参数para = va_arg( argp, char *);
if ( strcmp( para, “\0″) == 0 ) /* 采用空串指示参数输入结束 */printf(”参数 #%d 是: %s\n”, argno, para);argno++;//注意:栈底在高地址,栈顶在低地址,所以这里是++}va_end( argp ); /* 将argp置为NULL */return 0;}
//输出结果参数 #0 是: This参数 #1 是: is参数 #2 是: a参数 #3 是: demo!
注意到上面的例子没有使用第一个参数,下面的例子将使用所有参数
//================================ 例子程序2 ===============
#include #include int average( int first, … ); //输入若干整数,求它们的平均值
void main( void ){/* 调用3个整数(-1表示结尾) */printf( “Average is: %d\n”, average(2,3,4, -1));
/*调用4个整数*/printf( “Average is: %d\n”, average(5,7,9, 11,-1));
/*只有结束符的调用*/printf( “Average is: %d\n”, average(-1) );}
/* 返回若干整数平均值的函数 */int average( int first, … ){int count = 0, sum = 0, i =va_
va_start( marker, first ); //初始化while( i != -1 ){sum += //先加第一个参数count++;i = va_arg( marker, int);//取下一个参数}va_end( marker );return( sum ? (sum / count) : 0 );}
//输出结果Average is: 3Average is: 8Average is: 0
五、关于可变参数的传递问题
有人问到这个问题,假如我定义了一个可变参数函数,在这个函数内部又要调用其它可变参数函数,那么如何传递参数呢?上面的例子都是使用宏va_arg逐个把参数提取出来使用,能否不提取,直接把它们传递给另外的函数呢?
我们先看printf的实现:
int __cdecl printf (const char *format, …){va_
va_start(arglist, format); //arglist指向format后面的第一个参数
。。。//不关心其它代码retval = _output(stdout,format,arglist); //把format格式和参数传递给output函数
。。。//不关心其它代码return(retval);}
我们先模仿这个函数写一个:
#include #include
int mywrite(char *fmt, …){va_va_start(arglist, fmt);return printf(fmt,arglist);}
void main(){int i=10, j=20;char buf[] = “This is a test”;double f= 12.345;mywrite(”String: %s\nInt: %d, %d\nFloat :%4.2f\n”, buf, i, j, f);}
运行一下看看,哈,错误百出。仔细分析原因,根据宏的定义我们知道 arglist是一个指针,它指向第一个可变的参数,但是所有的参数都位于栈中,所以arglist指向栈中某个位置,通过arglist的值,我们可以直接查看栈里面的内容:
arglist -& 指向栈里面,内容包括
FD 67 00 //指向字符串”This is a test”A 00 00 00 //整数 i 的值 00 00 00 //整数 j 的值 3D 0A D7 //double 变量 f, 占用8个字节 B0 28 40 00 00 00
如果直接调用 printf(fmt, arglist); 仅仅是把arglist指针的值0067FD78入栈,然后把格式字符串入栈,相当于调用:
printf(fmt, 0067FD78);
自然这样的调用肯定会出现错误。
我们能不能逐个把参数提取出来,再传递给其它函数呢?先考虑一次性把所有参数传递进去的问题。
如果调用的是系统库函数,这种情况下是不可能的。因为提取参数是在运行态,而参数入栈是在编译的时候确定的。无法让编译器预知运行态的事情给出正确的参数入栈代码。而我们在运行态虽然可以提取每个参数,但是无法将参数一次性全部压栈,即使使用汇编代码实现起来也是很困难的,因为不单是一个简单的push代码就可以做到。
如果接受参数的函数也是我们自己写的,自然我们可以把arglist指针入栈,然后在函数中自己解析arglist指针里面的参数,逐个提取出来处理。但是这样做似乎没有什么意义,一方面,这个函数没有必要也做成可变参数函数,另一方面直接在第一个函数中解析参数,然后处理不是更简单么?
我们唯一可以做到的是,逐个解析参数,然后循环中调用其它可变参数函数,每次传递一个参数。这里又有一个问题,就是参数表中的不可变参数的传递问题,有些情况下不能简单的传递,以上面的例子为例, 通常我们解析参数的同时,还需要解析格式字符串:
#include #include #include
//测试一下这个,开个玩笑void t(…){printf(”\n”);}
int mywrite(char *fmt, …){va_va_start(arglist, fmt);
char temp[255];strcpy(temp, fmt); //Copy the Format stringchar Format[255];
char *p = strchr(temp,’%');int i=0;int iPdouble fPwhile(p != NULL){while((*p& 'a' || *p&‘z’) && (*p!=0) ) p++;if(*p == 0)p++;
//格式字符串int nChar = p -strncpy(Format,temp, nChar);Format[nChar] = 0;//参数if(Format[nChar-1] != ‘f’){iParam = va_arg( arglist, int);printf(Format, iParam);}else{fParam = va_arg( arglist, double);printf(Format, fParam);}
i++;if(*p == 0)strcpy(temp, p);p = strchr(temp, ‘%’);}if(temp[0] != 0)printf(temp);
void main(){int i=10, j=20;char buf[] = “This is a test”;double f= 123.456;mywrite(”String: %s\nInt: %d, %d\nFloat :%4.2f\nEnd”, buf, i, j, f, 0);t(”aaa”, i);}
//输出:String: This is a testInt: 10, 20Float :123.46End
当然这里的解析是不完善的。
分享给小伙伴们:
我来说两句……
微信公众账号1419人阅读
作者转自:
  下面介绍在C/C++里面使用的可变参数函数。
  先说明可变参数是什么,先回顾一下C++里面的函数重载,如果重复给出如下声明:
  int func();
  int func(int);
  int func(float);
  int func(int, int);
  这样在调用相同的函数名 func 的时候,编译器会自动识别入参列表的格式,从而调用相对应的函数体。
  但这样的方法毕竟有限,试想一下我们假如想定义一个函数,我们在调用之前(在运行期之前)根本不知道我到底要调用几个参数,并且不知道这些参数是个什么类型,例如我们想定义一个函数:
  int max(int n, ...);
  用来返回一串随意长度输入参数的最大值,例如调用
  max(3, 10, 20, 30)的时候,可以返回(n=3)个数 10,20,30 的最大值30。
  并且还可以接受任意个参数的输入,例如:
  max(6, 20, 40, 10, 50, 30, 40)也应该是被接受的,返回最大值50。
  这怎么达到呢?
  其实这样的例子我们肯定见过,最典型的就是 printf 函数,可以看 printf 函数的原形:
  int printf(char*, ...);
  它接受一个格式字符串,并且后面跟随任意指定的参数,根据实际需要而确定入参的个数。
  实际上它的实现要依赖于一个标准 C 库 &stdarg.h&,stdandard argument(标准参数) 的意思。下面先稍为介绍一下 &stdarg.h&,或者在 C++ 中的 &cstdarg& 的功效:
  这实际上是一组初始化和调用可变参数的宏,下面先介绍一下可变参数表的调用形式以及原理:
  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
  void func(int x, float y, char z);
  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x-&y-&z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
  然后是可变入参表格式,省略的参数用 ... 代替,但必须注意:
1. 只能有一个 ... 并且它必须是最后一个参数;
2. 不要只用一个 ... 作为所有的参数,因为从后面可以知道,这样你无法确定入参表的地址。
  举个例子,声明函数如下:
  void func(int x, int y, ...);
  然后调用:func(3, 5, 'c', 2.1f, 6);
  于是在调用参数的时候,编译器则不会检查实际输入的是什么参数,只管把所有参数按照上面描述的方法,变成实参堆放在内存中,在本例中,内存中依次存放 x=3, y=5, 'c', 2.1f, 6
  但是有一个需要注意的地方,这些东西只是紧挨着堆放在内存中,于是想要正确调用这些参数,必须知道他们确切的类型,并且我们也关心这个参数表实际的长度,然而不幸的是,这些我们无从得知。因此,这个解决办法决不是高明的,从某种程度上说,这甚至是一个严重的漏洞。因此,C++ 很不提倡去使用它。
  不过缺点归缺点,万不得已的时候我们还是得用,但是我们对里面输入变量的时候,应该对入参的类型有一个清醒的认识,否则这样的操作是很危险的。
  下面是 &stdarg.h& 对上面这一个思路的实现,里面重要的几个宏定义如下:
  typedef char* va_
  void va_start ( va_list ap, prev_param ); /* ANSI version */
  type va_arg ( va_list ap, type );
  void va_end ( va_list ap );
  其中,va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
&Step 1& 在调用参数表之前,应该定义一个 va_list 类型的变量,以供后用(下面假设这个 va_list 类型变量被定义为ap);
&Step 2& 然后应该对 ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量;
&Step 3& 然后是获取参数,调用 va_arg,它的第一个参数是 ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
&Step 4& 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。
  例如开始的例子 int max(int n, ...); 其函数内部应该如此实现:
int max(int n, ...) {& && && && && && & // 定参 n 表示后面变参数量,定界用,输入时切勿搞错
&&va_& && && && && && && && && & // 定义一个 va_list 指针来访问参数表
&&va_start(ap, n);& && && && && && && &&&// 初始化 ap,让它指向第一个变参
&&int maximum = -0x7FFFFFFF;& && && && &// 这是一个最小的整数
&&for(int i = 0; i & i++) {
& & temp = va_arg(ap, int);& && && && & // 获取一个 int 型参数,并且 ap 指向下一个参数
& & if(maximum & temp) maximum =
&&va_end(ap);& && && && && && && && && & // 善后工作,关闭 ap
// 在主函数中测试 max 函数的行为(C++ 格式)
int main() {
&&cout && max(3, 10, 20, 30) &&
&&cout && max(6, 20, 40, 10, 50, 30, 40) &&
  基本用法阐述至此,可以看到,这个方法存在两处极严重的漏洞:其一,输入参数的类型随意性,使得参数很容易以一个不正确的类型获取一个值(譬如输入一个float,却以int型去获取他),这样做会出现莫名其妙的运行结果;其二,变参表的大小并不能在运行时获取,这样就存在一个访问越界的可能性,导致后果严重的 RUNTIME ERROR。
  另外,&stdarg.h& 的内部实现形式在这处不再加说明,如果有需要可以参考下面的两个连接(感谢他们的作者)。
  作为建议,在 C++ 环境中尽量不要使用这种方法,如有需要,尽量先考虑使用类或者重载来代替,这样可以很好地弥补这种方法的漏洞。
全文完感谢读者,ELF原创,转载请注明出处
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:174648次
积分:2594
积分:2594
排名:第6850名
原创:65篇
转载:123篇
评论:24条
(1)(1)(1)(1)(3)(3)(5)(2)(1)(2)(4)(11)(8)(18)(8)(36)(6)(5)(2)(6)(5)(6)(3)(10)(20)(9)(1)(12)求助C++啊定义一个求两个数中较小值的函数模板min 要求如下啊 C++啊定义一个求两个数中较小值的函数模板min( ),要求在main( )函数中进行调用,求两个浮点型数据和两个整型数据中较小的数.还_作业帮
拍照搜题,秒出答案
求助C++啊定义一个求两个数中较小值的函数模板min 要求如下啊 C++啊定义一个求两个数中较小值的函数模板min( ),要求在main( )函数中进行调用,求两个浮点型数据和两个整型数据中较小的数.还
求助C++啊定义一个求两个数中较小值的函数模板min 要求如下啊 C++啊定义一个求两个数中较小值的函数模板min( ),要求在main( )函数中进行调用,求两个浮点型数据和两个整型数据中较小的数.还有这个问题 要有好的回答我会追加悬赏的!2.定义复数类,使用类成员函数方式重载运算符“-”,在main()函数中定义两个复数对象,计算、输出它们的差.类Person的定义如下,请实现该类,并创建对象obj,然后使用构造函数为obj赋予初始值(内容自定).class Person{private:charname[10];chartel[8];public:Person(char*xname,int xage,int xsalary,char *xtel);voiddisp();};
#include templateT min(T a,T b){return (a>b)?a:b;}int main(){int x=10;int y=20;std::cout阅读排行榜
评论排行榜}

我要回帖

更多关于 python 获取函数参数 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信