本文最后更新于:1 年前
简介
C语言允许定义参数数量可变的函数,称为可变参数函数,这种函数需要固定数量的强制参数和数量可变的可选参数,可选参数用…占位符表示。最经典的就是printf函数,其原型为:
1
| int printf(const char *format,...);
|
使用方法
- 需包含头文件 stdarg.h
- 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
- 在函数定义中创建一个 va_list 类型变量,用于获取可选参数,也被称为参数指针。
- 使用 va_start 宏 和 可变参数前的最后一个固定参数 来初始化 va_list 变量为一个参数列表。
- 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
- 使用宏 va_end 来清除赋予 va_list 变量的内存。
注意事项
- 定义的函数必须至少有一个固定参数;这么做的目的很明显,就是要定位变参的首地址,也就是固定参数地址+sizeof(固定参数类型);
- 固定参数和可变参数之间可以没有任何关系;虽然经典的printf函数中,第一个固定参数里(格式化字符串)包含了后续变参的类型,但是这仅仅是函数功能的需要,语法上没有任何要求,下面的例子可以证明这一点;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
double add(int n, ...) { int i = 0; double sum = 0.0; va_list argptr; va_start(argptr, n); for (i = 0; i < n; ++i) sum += va_arg(argptr, double); va_end(argptr); return sum; }
|
实现原理
可变参数的实现原理利用了内存的压栈技术,调用函数后函数参数会被压入(push)栈内,使用时,再逐个从栈里pop出来。按照默认的调用惯例,函数参数压栈的顺序是从最右边参数开始的,再向左逐个压入,根据栈的原理,在取完强制参数后,就从第一个可选参数开始取了。
在VS2017-X86平台下的源码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| typedef char* va_list; #define _ADDRESSOF(v) (&(v)) #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))) #define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) #define __crt_va_end(ap) ((void)(ap = (va_list)0)) #define va_start __crt_va_start #define va_arg __crt_va_arg #define va_end __crt_va_end #define va_copy(destination, source) ((destination) = (source))
|
参考链接:
https://blog.csdn.net/ericbar/article/details/79558827
https://blog.csdn.net/qq_41854911/article/details/121190006