va_start() - va_end() - va_arg() - va_copy() - va_list - Handle variable argument list (处理可变参数列表)

va_start{} - va_end{} - va_arg{} - va_copy{} - va_list - Handle variable argument list {处理可变参数列表}

  • 1. 可变参数列表
    • 1.1. `#include `
    • 1.2. 可变参数的限制
  • 2. macro `va_start()`
    • 2.1. Parameters
    • 2.2. Return Value
    • 2.3. Example
  • 3. macro `va_end()`
    • 3.1. Parameters
    • 3.2. Return Value
    • 3.3. Example
  • 4. macro `va_arg()`
    • 4.1. Parameters
    • 4.2. Return Value
    • 4.3. Example
  • References

1. 可变参数列表

参数列表的可变部分位于一个或多个普通参数 (命名参数) 的后面,它在函数原型中以一个省略号 (...) 表示。命名参数必须以某种形式提示可变部分实际所传递的参数数量,而且如果预先知道的话,也可以提供参数的类型信息。

当参数列表中可变部分的参数实际传递给函数时,它们将经历缺省参数提升。可变部分的参数只能从第 1 个到最后 1 个依次进行访问。

1.1. #include

可变参数列表是通过宏来实现的,这些宏定义于 stdarg.h 头文件,它是标准库的一部分。stdarg.h 头文件声明了类型 va_list 和宏 va_arg(), va_copy(), va_end(), and va_start()

#include   /* printf */
#include  /* va_list, va_start(), va_arg(), va_end() */

int FindMax(int num, ...) {
	int i, val, largest;
	va_list vl;
	va_start(vl, num);
	largest = va_arg(vl, int);
	for (i = 1; i < num; ++i) {
		val = va_arg(vl, int);
		largest = (largest > val) ? largest : val;
	}
	va_end(vl);

	return largest;
}

int main() {
	const int val = FindMax(7, 702, 422, 631, 834, 892, 104, 772);
	printf("The largest value is: %d\n", val);

	return 0;
}

参数列表中的省略号 (...) 提示此处可能传递数量和类型未确定的参数。在编写这个函数的原型时,也要使用相同的记法。

int FindMax(int num, ...); 函数声明了一个变量 va_list vl;,变量 va_list vl; 用于访问参数列表的未确定部分。变量 va_list vl; 通过调用 va_start(vl, num); 来初始化,第 1 个参数是 va_list 变量 va_list vl;,第 2 个参数是省略号 (...) 前最后一个有名字的参数。初始化过程把变量 va_list vl; 设置为指向可变参数部分的第 1 个参数 int num;

为了访问参数,需要使用 va_arg()va_arg(vl, int); 接受两个参数:变量 va_list vl; 和参数列表中下一个参数的类型。在这个例子中,所有的可变参数都是整型。va_arg(vl, int); 返回这个参数的值,并使 va_arg() 指向下一个可变参数。

最后,当访问完毕最后一个可变参数之后,我们需要调用 va_end(vl);

参数列表中至少要有一个命名参数。如果连一个命名参数也没有,你就无法使用 va_start()。这个参数提供了一种方法,用于查找参数列表的可变部分。

The largest value is: 892
请按任意键继续. . .

1.2. 可变参数的限制

可变参数必须从头到尾按照顺序逐个访问。如果你在访问了几个可变参数后想半途中止,这是可以的。如果你想一开始就访问参数列表中间的参数,那是不行的。

由于参数列表中的可变参数部分并没有原型,所有作为可变参数传递给函数的值都将执行缺省参数类型提升。

一个值的类型无法简单地通过检查它的位模式来判断,这两个限制就是这个事实的直接结果。

  1. 这些宏无法判断实际存在的参数的数量。
  2. 这些宏无法判断每个参数的类型。

要回答这两个问题,就必须使用命名参数。

如果你在 va_arg() 中指定了错误的类型,那么其结果是不可预测的。这个错误是很容易发生的,因为 va_arg() 无法正确识别作用于可变参数之上的缺省参数类型提升。charshortfloat 类型的值实际上将作为 intdouble 类型的值传递给函数。所以你在 va_arg() 中使用后面这些类型时应该小心。

2. macro va_start()

Initialize a variable argument list

void va_start(va_list arg_ptr, variable_name);

Initializes arg_ptr to retrieve the additional arguments after parameter variable_name.

A function that invokes va_start(), shall also invoke va_end() before it returns.

2.1. Parameters

  • va_list arg_ptr

Uninitialized object of type va_list.

After the call, it carries the information needed to retrieve the additional arguments using va_arg().

If va_list arg_ptr has already been passed as first argument to a previous call to va_start() or va_copy(), it shall be passed to va_end() before calling this function.

  • variable_name

Name of the last named parameter in the function definition. The arguments extracted by subsequent calls to va_arg() are those after variable_name.
函数定义中最后一个命名参数的名称。后续调用 va_arg() 提取的参数是 variable_name 之后的参数。

C
The parameter shall not be a parameter declared with register storage class, with function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions.
该参数不能是用寄存器存储类、函数或数组类型声明的参数,或与应用默认参数提升后得到的类型不兼容的类型声明的参数。

C++
The parameter shall not be of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter.

2.2. Return Value

none

2.3. Example

#include   /* printf */
#include  /* va_list, va_start(), va_arg(), va_end() */

void PrintFloats(int num, ...) {
	int i;
	double val;
	printf("Printing floats:");

	va_list vl;
	va_start(vl, num);
	for (i = 0; i < num; ++i) {
		val = va_arg(vl, double);
		printf(" [%.2f]", val);
	}
	va_end(vl);

	printf("\n");
}

int main() {
	PrintFloats(3, 3.14159, 2.71828, 1.41421);

	return 0;
}
Printing floats: [3.14] [2.72] [1.41]
请按任意键继续. . .

3. macro va_end()

End using variable argument list

void va_end(va_list arg_ptr);

Performs the appropriate actions to facilitate a normal return by a function that has used the va_list object arg_ptr to retrieve its additional arguments.

This macro should be invoked before the function returns whenever va_start() has been invoked from that function.

3.1. Parameters

  • va_list arg_ptr

va_list object previously initialized by va_start() or va_copy().

3.2. Return Value

none

3.3. Example

#include   /* puts */
#include  /* va_list, va_start(), va_arg(), va_end() */

void PrintLines(char* first, ...) {
	char* str = first;
	va_list vl;
	va_start(vl, first);

	do {
		puts(str);
		str = va_arg(vl, char*);
	} while (str != NULL);

	va_end(vl);
}

int main() {
	PrintLines("First", "Second", "Third", "Fourth", NULL);
	return 0;
}

The PrintLines function takes a variable number of arguments. The first argument passed becomes parameter first, but the remaining are retrieved sequentially in the do-while loop using va_arg() until a null pointer is retrieved as the next argument.

First
Second
Third
Fourth
请按任意键继续. . .

4. macro va_arg()

Retrieve next argument

variable_type va_arg(va_list arg_ptr, variable_type);

This macro expands to an expression of type type with the value of the current argument in the variable arguments list identified by va_list arg_ptr.
该宏扩展为类型为 type 的表达式,其值为 va_list arg_ptr 标识的变量参数列表中的当前参数

Each call to this macro modifies the state of arg_ptr so that the next call to this macro will expand to the argument that follows the one it evaluates to.
每次调用这个宏都会修改 arg_ptr 的状态,以便下次调用这个宏时会扩展为它所计算的参数之后的参数。

Notice that va_arg() cannot determine the actual type of the argument passed to the function, but uses whatever type is passed as the type macro argument as its type.
请注意,va_arg() 无法确定传递给函数的参数的实际类型,而是使用作为 type 宏参数传递的任何类型作为其类型。

Notice also that va_arg() does not determine either whether the retrieved argument is the last argument passed to the function (or even if it is an element past the end of that list). The function should be designed in such a way that the number of parameters can be inferred in some way by the values of either the named parameters or the additional arguments already read.
还要注意的是,va_arg() 既不能确定检索到的参数是否是传递给函数的最后一个参数 (甚至不能确定它是否是该列表末尾的元素)。函数的设计方式应使得可以通过命名参数或已读取的附加参数的值以某种方式推断出参数的数量。

4.1. Parameters

  • va_list arg_ptr

Object of type va_list carrying information about the current retrieval state of a variable argument list. This object shall have been initialized by an initial call to va_start() or va_copy() and not have been released with va_end().
类型为 va_list 的对象,包含有关变量参数列表的当前检索状态的信息。此对象应已通过对 va_start() or va_copy() 的初始调用进行初始化,并且尚未通过 va_end() 释放。

4.2. Return Value

Returns the current additional argument as an expression of type type.

4.3. Example

References

[1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/
[2] va_arg() - va_copy() - va_end() - va_start() - Handle Variable Argument List, https://www.ibm.com/docs/en/i/7.5?topic=lf-va-arg-va-copy-va-end-va-start-handle-variable-argument-list

你可能感兴趣的:(C,va_start,va_end,va_arg,va_copy,va_list,处理可变参数列表)