设为首页 加入收藏

TOP

C语言中变参函数的实现细节(一)
2017-07-25 10:22:30 】 浏览:7491
Tags:言中 函数 实现 细节

C语言的函数虽然不具备C++的多态性,但也可以接受参数不确定的情况,当然,C语言中的变参函数实际在功能上是受限的,废话不多讲,下面来看看变参函数的边边角角的问题。

讨论之前我们来看一下最熟悉的变参函数printf的原型声明:

--------------------------------------------------------------------------------

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

--------------------------------------------------------------------------------

注意到,在函数中声明其参数是可变的方法是三个点“...”,但同时,这个函数必须要有一个固定的参数,比如printf里面的这个format,也就是说变参函数的参数数目至少是一个。这是由C语言中实现变参的原理---计算堆栈地址---决定的。顺着printf函数我们来看看它的定义是什么:

--------------------------------------------------------------------------------

int __printf(const char *format, ...)

{

va_list arg;

int done;

va_start(arg, format);

done = vfprintf(stdout, format, arg);

va_end(arg);

return done;

}

--------------------------------------------------------------------------------

(注意到库函数中内部定义的变量和函数用了双下划线开头,这也是我们写应用程序时尽量不要用双下划线开头的原因,我们也不应该使用单下划线开头的函数和变量,因为那也是系统保留的)

其中发现__printf函数里用了va_list,va_start,va_end等宏,事实上,在__printf中调用的vfpirntf函数还用到了一个叫做va_arg的宏,这几个宏就是编写变参函数的关键。现在我们自己写一个最简单的变参函数,先来个感性认识:

--------------------------------------------------------------------------------

#include

#include

void simple_va_fun(int i, ...)

{

va_list arg_ptr; //定义一个用来指向函数变参列表的指针arg_ptr

int j;

va_start(arg_ptr, i); //使arg_ptr指向第一个可变参数

j = va_arg(arg_ptr, int); //取得arg_ptr当前所指向的参数的值,并使arg_ptr指向下一个参数

va_end(arg_ptr); //指示提取参数结束

printf("%d %d\n", i, j);

return;

}

int main(void)

{

simple_va_fun(3, 4);

return 0;

}

--------------------------------------------------------------------------------

如代码中的注释所示,arg_ptr实际上是一个指向函数变参列表的指针,va_list实际上是void指针类型。

va_start用来初始化这个指针,使之指向变参列表中的第一个参数,注意到它的第二个参数是变参函数的那个固定参数。

va_arg利用已经初始化了的arg_ptr指针来取得变参列表中各个参数的值,第一个参数是变参列表指针,第二个参数是当前参数的类型。

va_end宏用来提示结束参数结束,在LINUX的glibc实现中,va_end实际上就是一个空语句(void)0

各个宏定义在头文件stdarg.h中声明,因此我们需要包含这个头文件。其具体的定义如下:

--------------------------------------------------------------------------------

#define _AUPBND (sizeof(acpi_native_int) - 1)

#define _ADNBND (sizeof(acpi_native_int) - 1)

#define _bnd(X, bnd) (((sizeof(X)) + (bnd)) & (~(bnd)))

#define va_start(ap, A) (void)((ap) = (((char *)&(A)) + (_bnd(A, _AUPBND)))

#defind va_arg(ap, T) (*(T*)(((ap) += (_bnd(T, _AUPBND))) - (_bnd(T, _ADNBDN))))

#define va_end(ap) (void)0

--------------------------------------------------------------------------------

这些宏定义都比较繁琐,主要目的是为了适应不同系统的地址对齐问题。

上面说过,va_start的功能实际上是使ap指针指向第一个变参,A就是我们的第一个固定参数,不考虑地址对齐,最简单的办法当然如下:

ap = &A + sizeof(A)

上述代码其实也是实现的这个简单的功能,但经过宏_AUPBND和_bnd之后,就能保证ap指向的地址至少是关于acpi_native_int对齐的,打个比方,如果此时A的地址是0x0003,而且A的类型占用4个字节,而当前系统要求4个字节对齐,那么就让_AUPBND中的sizeof参数为4,经过多次宏替代之后ap的地址值就会是0x0008,而简单地用上面的算式ap = &A + sizeof(A)计算出的结果是0x0007。

同样地,va_arg宏替代在不考虑任何移植性问题时,要取得当前变参的值并使指针指向下一个参数最简单的办法如下:

*((ap+=sizeof(T)) - sizeof(T))

这个需要稍微解释一下,首先,C里面的参数压栈是从右到左顺序压栈的,因此可以想象,第一个固定参数在栈顶(LINUX进程映像中栈是倒着增长的,这个地址是所有参数中最小的),第二个参数(也就是第一个变参)在紧接着固定参数之上,以此类推。因此,要想ap指针不断指向下一个参数,就必须让它每次都加上当前指向的变量所占内存的大小即 ap+=sizeof(T) 的含义。

接下来,利用这个地址值又减去sizeof(T),实际上地址值又回到上一个参数处(注意,此时ap指针的值并未改变,也就是说,va_arg宏实现获取第一个变参的值的时候是先使ap指向第二个变参,然后再去获取第一个变参的值),然后取值

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇linux下c语言入门04 main函数的编.. 下一篇C陷阱与缺陷第一章

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目