C语言可变参数设计的理解

曾经看过C语言可变参数函数的设计原理,今天与同事交流的时候居然没有话题,全忘了。赶快记下来。顺便做一些理解。
为了使用C可变参数的一整套机制,第一步就是要包含其头文件
#include <stdarg.h>
这样前提工作就做好了,免得当在你自己的可变函数内部定义
va_list var_ptr
报出以下错误
error: 'va_list' was not declared in this scope
然后,就让我们使用可变参数相关的变量实现功能吧。我在此也不想过多的描写可变参数相关变量的使用方法,只想描述一下它的实现原理。
首先还是看看几个变量的实现
va_list atg_ptr;
typedef char* va_list;//va_list就是一个char*的类型
va_start()宏函数的定义如下
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 
按照va_start()的使用方法,这个v就是你的可变参数函数中最后一个固定参数,比如说printf()函数,就是双引号“”中的参数。
这个宏函数的作用按照字面理解就是使ap指向固定参数v后面那个参数。既然是后面那个,到底是哪个呢?这就由偏移值_INTSIZEOF(v)决定了
_INTSIZEOF()也是个宏函数,它的定义如下
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
一眼看上去真不理解是啥意思,不过将这个宏单独拿出来做了几个实验之后发现,它的返回值是4的倍数,例如4,8。
好了,到目前为止va_start()的作用介绍完毕了,接下来看看下一个函数va_arg()
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

这个宏有点复杂,因为()太多了,剥离一下看看

ap += _INTSIZEOF(t); //先使ap指向下一个参数
*(t*)(ap - _INTSIZEOF(t))//在用已经指向下一个参数的指针减去偏移量取值
哦,原来是这个意思,每一次调用va_arg(),按照效果来看是先返回当前的值,再把指针指向下一个参数
最后,当参数全部取完了,该做收尾工作了调用
va_end()
#define va_end(ap) ( ap = (va_list)0)
使指针指向0,防止野指针
以上是关于可变参数相关变量的使用说明,有以下几点需要注意的。
1.可变参数重在一个“变”字,那么如何控制变的数目呢?必须指定可变参数的数目,所以可变参数前固定参数中必须要有一个变量用来指定可变参数的数目。
对于printf()函数,它的可变参数的数目是由第一个参数字符串“”中%d,%f,%c等的数目决定的。
2.为什么va_start()的宏实现中是"+"号而不是减号?因为当前C语言函数入栈的顺序是从右往左的,最有边的参数在高地址,越往左参数所在的地址越低。形如下图

void fun(int a,int b,int c) {}
           a		  b		          c
.....|___________|____________|_____________|.......
   0x01     0x02	        0x03	       0x04    地址
假定a是固定参数,b和c是可变参数,如果知道了a的地址,那么b和c的地址只要加上适当的偏移就可以得到了。
3._INTSIZEOF()做了一个什么事?这个宏估计就是为了实现一个准则:如果一个函数的形参类型未知, 例如使用了Old Style C风格的函数声明,或者函数的参数列表中有 ...,那么调用函数时要对相应的实参做Integer Pormotion,此外,相应的实参如果是float型的也要被提升为double类型,这条规则称为Default Argument Promotion"。关于float类型的提升,我猜printf()源码里面势必有这样的逻辑
if(*arg == 'f')
{
     double tmp;
     tmp = va_arg(va_ptr,double);
}
这将是下一个研究的地方


谢谢观赏!-_-|||排版太搓了,见谅。

你可能感兴趣的:(C语言可变参数设计的理解)