在C/C++函数中使用可变参数,
下面介绍在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_list;
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, ...); 其函数内部应该如此实现:
#include <iostream>
#include <cstdlib>
#include <iomanip>
#include <cstdarg>
using namespace std;
int getMaxValue(int n, ...){
va_list ap;
va_start(ap,n);
int temp;
int max_result = 0;
for(int i = 0;i < n;i++) {
temp = va_arg(ap,int);
if(temp > max_result) {
max_result = temp;
}}
va_end(ap);
return max_result;
}
int main(int argc,char *argv[]){
std::cout << "hello world" << setw(5)<<
getMaxValue(5,1,23,44,55,44) << std::endl;
cout << getMaxValue(6, 20, 40, 10, 50, 30, 40) << endl;
return EXIT_SUCCESS;
}
输出结果为:
hello world 55
50
基
本用法阐述至此,可以看到,这个方法存在两处极严重的漏洞:其一,输入参数的类型随意性,使得参数很容易以一个不正确的类型获取一个值(譬如输入一个
float,却以int型去获取他),这样做会出现莫名其妙的运行结果;其二,变参表的大小并不能在编译时获取,这样就存在一个访问越界的可能性,导致后
果严重的 RUNTIME ERROR。
分享到:
相关推荐
### C/C++可变参数函数的参数传递机制剖析 #### 摘要 本文深入探讨了C/C++语言中可变参数函数的参数传递机制,并提出了一种更加精确且灵活的设计方法来处理这类函数。通过分析,我们不仅理解了如何在函数内部访问...
本文将深入探讨如何在C/C++中使用可变参数。 首先,可变参数在函数声明中通过省略号(...)表示,如`int printf(const char *, ...)`. 这样的函数可以接受任意数量的参数,但通常需要一个固定数量的非变参数作为...
总的来说,这份压缩包提供的资料全面覆盖了C和C++的基础和高级功能,是学习和开发过程中不可或缺的工具。无论是初学者学习基本语法,还是资深开发者查找特定函数的用法,都能从中获益。在实际编程中,熟悉并熟练运用...
### C语言中可变参数的用法 #### 引言 在C语言中,经常会遇到参数个数可变的函数,比如`printf()`函数。这类函数不仅能够接收一个固定类型的参数,还可以根据需要接受数量不等的其他参数。本文将详细介绍如何在...
这种方式允许C/C++支持可变参数列表,因为调用者可以根据实际需要压入任意数量的参数,而无需被调用者事先知晓参数的数量。 ### __stdcall `__stdcall`,即“标准调用”,是Windows API广泛采用的一种调用约定。与...
OSIP库包含了解析器、构建器和事务管理器等组件,使得在C/C++中实现SIP功能变得更加便捷。例如,通过`osip_message_init()`初始化一个SIP消息对象,然后使用`osip_message_parse()`解析接收到的SIP报文,`osip_...
7. **C/C++语言可变参数表深层探索** - **可变参数列表**:了解如何使用 `va_list`、`va_start` 和 `va_end` 等宏来处理可变数量的参数。 8. **C/C++数组名与指针区别深层探索** - **数组名与指针的区别**:虽然...
- **使用CONST**:通过使用`const`关键字来限制变量或参数的可变性,从而提高函数的健壮性。 - **提高效率**:给出一些实用的技巧来提高程序运行时的性能。 - **有益建议**:分享作者在实践中积累的宝贵经验和教训。...
总的来说,《C/C++函数集合.CHM》是学习和工作中不可或缺的工具,它能帮助你深入理解C/C++函数的使用,提高编程技能,同时,双语版本的设计也增加了其普适性。无论你是自学编程的新手,还是在项目开发中寻求解决方案...
`switch`语句的参数不能为浮点类型,这是因为浮点数的比较在C/C++中不精确,可能导致预期之外的结果。 #### 局部变量与全局变量重名 局部变量可以与全局变量同名,局部变量在函数内优先级高于全局变量,因此在函数...
最优化算法是计算机科学和...在实际应用中,需要根据问题的特性和需求选择合适的最优化算法,并在C/C++中实现和调整参数以获得最佳效果。同时,理解这些算法的原理并进行调试,可以帮助我们更好地理解和改进优化过程。
对于可变参数函数(如printf),则需要特别的处理策略来正确解析参数。 ATPCS还定义了结果返回的机制。对于整数和指针类型的结果,通常通过R0寄存器返回。浮点结果则通过浮点寄存器传递,对于支持向量浮点运算的...
在C/C++编程中,可变参数是一种强大的特性,它允许函数接受任意数量或类型的参数。这主要通过标准库中的头文件提供的机制实现。本文将深入探讨如何在C/...理解并正确使用`va_list`和相关宏是掌握C/C++可变参数的关键。
在C/C++中,调试工具可以帮助开发者定位问题所在,并提供修改代码的方法。 #### Argument 参数,论点 在编程中,“argument”通常指的是传递给函数的值。函数可以根据这些值执行不同的操作或计算。 #### Swap 交换...
在C/C++编程中,可变参数是一种处理不定数量参数的方法。这主要通过一组预定义的宏来实现,包括`va_list`、`va_start`、`va_arg`和`va_end`,这些宏定义在`stdarg.h`头文件中。下面我们将详细探讨这些宏的用途和工作...
### C/C++语言中调用汇编语言的方法与实现 #### 一、引言 随着计算机技术的发展,C/C++语言因其高效性、灵活性及良好的跨平台能力,在软件开发领域得到了广泛的应用。与此同时,汇编语言作为一种底层语言,能够...