文章目录
一、可变参函数
在C语言编程中,我们经常会遇到一些参数个数可变的函数,如:int printf(const char *format, ...);
、scanf()
等等。它除了第一个参数format固定以外,后面跟着的参数的个数和类型是可变的,其中...
称作参数占位符。
可是上述中的可变参函数是如何对变化的参数进行处理的呢?我们又是否可以编写属于自己的可变参函数呢?当然可以,不过需要首先来认识一下va_list
的成员和功能。
1、va_list简介
va_list
是C语言中用于解决变参问题的一组宏,它定义于标准库stdarg.h
中,通过命令$ man va_arg
也可以从手册中查询到va_list
的用法,下面将详细介绍va_list
各成员的功能和实现原理。
2、va_list成员
成员 | 原型 | 描述 | 版本 |
---|---|---|---|
va_start | void va_start(va_list ap, last); |
初始化va_list类型的变量ap | C89 |
va_arg | type va_arg(va_list ap, type); |
返回可变参数中类型为type的参数 | C89 |
va_end | void va_end(va_list ap); |
释放指定的va_list | C89 |
va_copy | void va_copy(va_list dest, va_list src); |
拷贝va_list的内容 | C99 |
3、va_list原理
由man手册可知,va_list
相关成员定义在stdarg.h
文件中,经过查找,该头文件的路径为:/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h
。当前PC使用的GCC版本是gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
。
#define va_start(v,l) __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L || defined(__GXX_EXPERIMENTAL_CXX0X__)
#define va_copy(d,s) __builtin_va_copy(d,s)
#endif
#define __va_copy(d,s) __builtin_va_copy(d,s)
从上述定义中,可以发现va_list
成员都是通过GCC内建函数实现的。这部分对于非编译器研究者的我来说,实在有些晦涩,难以跟踪。因此暂时就先不继续探究了。
4、va_list用法
1)printk函数
printk()
是内核空间的打印函数,其实现中也借助了va_list
处理可变参数。printk()
原码如下所示:
asmlinkage __visible int printk(const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = vprintk_func(fmt, args);
va_end(args);
return r;
}
EXPORT_SYMBOL(printk);
具体实现就不再此处分析了,后面会单独分析Linux printk()
实现的原理。
2)自定义函数
/* @file: a.c */
#include <stdio.h>
#include <stdarg.h>
static void show_numbers(int num, ...)
{
va_list va;
va_start(va, num);
while(num--) {
printf("%d\n", va_arg(va, int));
}
va_end(va);
}
int main(void)
{
show_numbers(3, 110, 120, 114);
return 0;
}
以下是程序运行结果: