可变长度参数如何工作?
在本文中,我们将讨论可变长度参数的工作原理。
- 可变函数
- 调用约定
- C/C++程序的内存布局
- 负下标
可变参数函数:一个可变参数函数是采用可变长度参数的模板。可变长度参数是一种功能,它允许函数接收任意数量的参数。在某些情况下,函数根据要求处理可变数量的参数,例如:
- 给定数字的总和。
- 给定数字的最小值等等。
可变数量的参数由三个点 (...) 表示。
方案一:
C
// C program to demonstrate the use of
// variable number of arguments
#include
#include
// Function to find the minimum of integer
// numbers passed, ftrst argument is count
// of numbers
int min(int arg_count, ...)
{
int i;
int min, a;
// va_list is a type that holds the
// information about variable arguments
va_list ap;
// va_start must be called before
// accessing variable argument list
va_start(ap, arg_count);
// Now arguments can be accessed one
// by one using va_arg macro.
// Initialize min as the first
// argument in list
min = va_arg(ap, int);
// Traverse the rest of arguments
// to find out minimum
for (i = 2; i <= arg_count; i++)
if ((a = va_arg(ap, int)) < min)
min = a;
// va_end should be executed before
// the function returns whenever
// va_start has been previously
// used in that function
va_end(ap);
return min;
}
// Driver Code
int main()
{
int count = 5;
printf("Minimum value is %d",
min(count, 12, 67, 6, 7, 100));
return 0;
}
C++
// C++ program to implement stdarg.h
#include
#include
using namespace std;
// Function to find the sum of numbers
int sum(int num, ...)
{
int res = 0;
va_list ap;
va_start(ap, num);
for (int i = 0; i < num; i++) {
res += va_arg(ap, int);
}
// Return the resultant sum
return res;
}
// Driver Code
int main()
{
// First argument is the number
// of arguments
cout << sum(4, 6, 89, 34, 26);
return 0;
}
Minimum value is 6
调用约定:调用约定是指函数如何调用,参数如何传递,栈是如何清理的。 C/C++ 有多种调用约定,但我们只关心__cdecl和__stdcall 。
两者非常相似,但有一些差异。
- __cdecl 是 C/C++ 的默认调用约定。
- __stdcall 是 Windows API 函数的默认调用约定。
现在两个调用约定都从右到左传递参数。 C/C++ 开发人员选择从右到左而不是从左到右。
内存布局:内存布局讨论如下:
唯一需要注意的是堆栈和堆段的增长方向相反。
- 堆向更高的地址增长。
- 堆栈向低地址增长。这意味着堆栈中的较高地址在地址中较低。当我们将某些东西压入堆栈时,它会获得堆栈中最低的立即数。
让我们通过函数理解这一点:
Inside main() we have a function func(arg1, arg2, arg3)
When func is called then main() is called “caller” and func() is called “callee”
Lets see its stack
caller local variable -> lower on stack higher on address
—- (other stuffs)
arg3 (rightmost)
arg2
arg1
—-
callee local variables -> higher on stack lower on address ^ new stack are created here not at top
在上一节中,您可以清楚地看到第一个参数获得了最低地址。这就是为什么开发人员选择从右到左而不是从左到右的原因,因为从左到右的调用约定会给第一个参数提供可能导致问题的最高地址。
The first argument gets the lowest address and all parameters will have a continuous address in the stack.
负下标: []是下标运算符。以下是有关下标运算符的一些重要注意事项:
- 如果下标运算符对指针进行操作,则其行为不同。即,ptr[x] 表示 *(ptr + x),即 ptr 之前 x*sizeof(data_type_of_pointer) 处的值。
- 类似地,ptr[-x] 表示 *(ptr – x),即 ptr 后面 x*sizeof(data_type_of_pointer) 处的值。
下面是一个展示
C++
// C++ program to implement stdarg.h
#include
#include
using namespace std;
// Function to find the sum of numbers
int sum(int num, ...)
{
int res = 0;
va_list ap;
va_start(ap, num);
for (int i = 0; i < num; i++) {
res += va_arg(ap, int);
}
// Return the resultant sum
return res;
}
// Driver Code
int main()
{
// First argument is the number
// of arguments
cout << sum(4, 6, 89, 34, 26);
return 0;
}
155
解释:
内置实现有一些限制,让我们来看看它们,看看如何克服它。其中之一是需要强制传递第一个参数,让我们看看为什么它是强制性的以及如何避免它。
- 他们在做什么?
- 我们如何复制它?
va_list的:它是char *的类型定义,但是这是完全不同的,当它被用作C型字符串,我们得到不可预知的结果的。这是因为它不是常见的typedef 。它是内置定义的。
// arg.h
typedef char* va_list;
va_start:这是一个宏,它的作用是初始化 ap,它实际上是va_list类型( char* ),在第一个参数arg1之前有一个地址。这就是为什么需要强制传递第一个参数的原因。它可以是任何数据类型的任何值,但为了简单起见,通常传递参数的数量。它用于标识堆栈上参数连续的地址。
va_arg:这个宏相当复杂。它做两件事。
- 返回所需的参数
- 前进到下一个参数
// arg.h
#define va_get(ap, type) ((type*)ap)
// casts address held by ap (here arg2) into type*
#define va_advance(ap, type) ap = ap + sizeof(type)
让我们看看这三个点是什么。实际上,它是一个eclipse运算符( . . )并且它是 C++ 定义的。此运算符用于传递可变数量的参数。如果无法使用可变参数函数的第一个参数,这就是stdarg.h 的工作方式。我们可以利用它作为
#define va_start(ap, arg1) (ap = (char*)(&arg1))
但是,这没有意义,因为我们不知道传递了多少个参数,那么我们如何使用它们呢?