也谈 C 语言变长参数 - [语言探索]
Tag:语言探索
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://bigwhite.blogbus.com/logs/20468193.html
很多技术人员都有在"技术细节"上"钻牛角尖"的"癖好",对此很多人褒贬不一;无论怎样,我也是属于这类
人。C 语言的变长参数在平时做开发时很少会在自己设计的接口中用到,但我们最常用的接口 printf 就是
使用的变长参数接口,在感受到 printf 强大的魅力的同时,是否想挖据一下到底 printf 是如何实现的呢?
这里我们一起来挖掘一下 C 语言变长参数的奥秘。
先考虑这样一个问题:如果我们不使用 C 标准库(libc)中提供的 Facilities,我们自己是否可以实现拥有变长
参数的函数呢?我们不妨试试。
一步一步进入正题,我们先看看固定参数列表函数,
void fixed_args_func(int a, double b, char *c) {
printf("a = 0x%p", &a);
printf("b = 0x%p", &b);
printf("c = 0x%p", &c);
}
对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,
比如:通过&a 我们可以得到 a 的地址,并通过函数原型声明了解到 a 是 int 类型的; 通过&b 我们可以得到
b 的地址,并通过函数原型声明了解到 b 是 double 类型的; 通过&c 我们可以得到 c 的地址,并通过函数原
型声明了解到 c 是 char*类型的。
但是对于变长参数的函数,我们就没有这么顺利了。还好,按照 C 标准的说明,支持变长参数的函数在原
型声明中,必须有至少一个最左固定参数(这一点与传统 C 有区别,传统 C 允许不带任何固定参数的纯变长
参数函数),这样我们可以得到其中固定参数的地址,但是依然无法从声明中得到其他变长参数的地址,比
如:
void var_args_func(const char * fmt, ... ) {
... ...
}
这里我们只能得到 fmt 这固定参数的地址,仅从函数原型我们是无法确定"..."中有几个参数、参数都是什
么类型的,自然也就无法确定其位置了。那么如何可以做到呢?在大脑中回想一下函数传参的过程,无论
"..."中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈
操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,
我们完全有可能推导出其他变长参数的位置,顺着这个思路,我们继续往下走,通过一个例子来诠释一下:
(这里要说明的是:函数参数进栈以及参数空间地址分配都是"实现相关"的,不同平台、不同编译器都可能
不同,所以下面的例子仅在 IA-32,Windows XP, MinGW gcc v3.4.2 下成立)
我们先用上面的那个 fixed_args_func 函数确定一下这个平台下的入栈顺序。
int main() {
fixed_args_func(17, 5.40, "hello world");
return 0;