C语言-可变参数函数

本文最后更新于:1 年前

简介

C语言允许定义参数数量可变的函数,称为可变参数函数,这种函数需要固定数量的强制参数和数量可变的可选参数,可选参数用…占位符表示。最经典的就是printf函数,其原型为:

1
int printf(const char *format,...);

使用方法

  1. 需包含头文件 stdarg.h
  2. 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  3. 在函数定义中创建一个 va_list 类型变量,用于获取可选参数,也被称为参数指针。
  4. 使用 va_start 宏 和 可变参数前的最后一个固定参数 来初始化 va_list 变量为一个参数列表。
  5. 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  6. 使用宏 va_end 来清除赋予 va_list 变量的内存。

注意事项

  1. 定义的函数必须至少有一个固定参数;这么做的目的很明显,就是要定位变参的首地址,也就是固定参数地址+sizeof(固定参数类型);
  2. 固定参数和可变参数之间可以没有任何关系;虽然经典的printf函数中,第一个固定参数里(格式化字符串)包含了后续变参的类型,但是这仅仅是函数功能的需要,语法上没有任何要求,下面的例子可以证明这一点;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数add() 计算可选参数之和
// 参数:第一个强制参数指定了可选参数的数量,可选参数为double类型
// 返回值:和值,double类型
double add(int n, ...)
{
        int i = 0;
        double sum = 0.0;
        va_list argptr;
        va_start(argptr, n); // 初始化argptr
        for (i = 0; i < n; ++i) // 对每个可选参数,读取类型为double的参数,
                sum += va_arg(argptr, double); // 然后累加到sum中
        va_end(argptr);
        return sum;
}

//double s = add(5, 1.0, 2.0, 3.0, 4.0, 5.0);

实现原理

可变参数的实现原理利用了内存的压栈技术,调用函数后函数参数会被压入(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