最近学习linux系统编程,看到很多函数都有可变参数,就想了解一下可变参数具体怎么实现的,在网上找了几个文章,都不怎么易懂,结合他们写的以及去看源码实现,才彻底理解,写下这篇博客,以后自己忘了再来看看
前提小知识: 函数参数是从右往左依次压入栈的,最低地址cnt,最高地址为最后一个参数
先来看一个小demo
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
void sum(int cnt, ...)
{
va_list args;
va_start(args, cnt);
int i = 0;
int ret = 0;
while (i < cnt)
{
ret += va_arg(args, int);
++i;
}
va_end(args);
printf("ret = %d\n", ret);
}
int main()
{
sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
return 0;
}
以上是一个用可变参数实现的sum函数,第一个参数为总共有几个数相加,后面则是要进行相加的参数,首先介绍一下va_list 从名字可以看出这是一个链表,下面是va_list源码
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
这是一个链表,实际上并没有链子,它只指向一块空间,他会在va_start进行初始化,下面是va_start,va_args,va_end 宏函数源码
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
_INTSIZEOF(N)
这个宏是用做内存对齐的,如果N的大小在1 - 4 之间那么他会返回4,4 - 8之间会返回8,内置类型,效果和sizeof没差
va_start
ap是va_list结构体变量,v是函数参数中的第一个参数,将v强转为va_list类型,再加v本身大小,就能找到可变参数的第一个参数地址,也就是sum函数的第二个参数地址,因为v是第一个参数,加上他本身的大小就是跳过了自身,来到了紧邻自身后的第一个地址,这点相信有C指针基础的可以很容易理解,而通过前提知识,参数地址是紧挨着的,所以加了第一个参数大小,就到了第二个参数,并且让ap指向了这一参数地址
va_arg
ap依旧是va_list类型,t为type简写,ap+=t本身大小后返回一个ap加之前的值,并强转为t*再解引用,
如果看不懂这个,就把他想象成 i++后,返回的是i加之前的值,是一回事,这个宏函数的作用就是取出ap的值,ap在start时指向了第二参数,也就是取第二参数的值,并将ap指向下一个参数地址
va_end
这个就很简单了,就是把ap置位空指针,C的空指针就是0
明白这三个后,再去看一下前面的小demo就非常轻松易懂了,也就能理解C语言可变参数是怎么实现的了