深度剖析C语言可变参数列表

7 篇文章 0 订阅
1 篇文章 0 订阅

C语言可变参数函数分析与实现

1.可变参数的函数声明:

void PrintFloats ( int amount, ...);
注:声明中函数的第一个参数名必须是可知的(如例amout参数名开始,而非直接...开始)因为要通过宏确定其传入参数起始地址及个数等问题。
 
  

2.使用可变参数函数的具体实现过程:

/* va_start example */
#include <stdio.h>
#include <stdarg.h>

void PrintFloats ( int amount, ...)
{
  int i;
  double val;
  printf ("Floats passed: ");
  va_list vl; 		//定义va_list 变量用于确定参数
  va_start(vl,amount);	//确定函数传入参数的第一个地址
  for (i=0;i<amount;i++)
  {
    val=va_arg(vl,double);//相继的去处下一个参数,然后进行操作
    printf ("\t%.2f",val);
  }
  va_end(vl);		//结束标志,这个宏只是看起来和va_start 对称而已
  printf ("\n");
}

int main ()
{
  PrintFloats (3,3.14159,2.71828,1.41421);
  return 0;
}

3.可变参数的内部实现

相关类型的原型(在代码中右击跳转到定义):
typedef char *  va_list;
#define va_start  _crt_va_start
#define va_arg  _crt_va_arg
#define va_end  _crt_va_end

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)      ( ap = (va_list)0 )

#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

具体分析:
#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
这个宏通过类型转换取得参数v的地址。也就是如上例子中的&amount


#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
由参数的传递过程来看,这里的n就是上边的v也就是&amount
那么这个式子就是求参数的某个大小了。:
#define _INTSIZEOF(n)    对于上式amount 为int类型有:
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
=((4         + 4           - 1) & ~(4           - 1) )
=((7                          ) & ~(3                )
=  0111  &  ~(0011)
=  0111  &  ~(1100)
=  0100 = 4(这不正是int的字节对齐数么)

#define _INTSIZEOF(n)   假设传入参数为char类型:
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
=((1         + 4           - 1) & ~(4           - 1) )
=((4                          ) & ~(3                )
=  0100  &  ~(0011)
=  0100  &  ~(1100)
=  0100 = 4(这不正是x86下的默认字节齐数么)


可看出这个只是什么东东了?  它就是传说中的字节对齐数处理。由于传入参数压栈是由此规矩搞定,那么现在使用我们也就需要用到他了。


#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
有以上的分析,对此式子有:ap =  (char*)(&amount)+(amount参数的字节对齐数)   //也就是下一个变量的地址了。


#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )


所以,实际上,该表达式是反悔了当前地址的变量,而将ap指向下一个地址。


#define _crt_va_end(ap)      ( ap = (va_list)0 )
这个就简单了,设置ap = (char*)0;  也就是设置为空。其实这句是可有可无的,只是和start对称,程序的完整性。。或者你也可以理解为他是安全着想(然而影响并没有什么)

至此,我们已经将所有相关的宏进行解读剖析完成。可变参数也就成为了我们使用的一大利器。

4.扩展

另外补充一点的是,这个参数中,最重要的就是对字节对齐的处理了。。这个问题是平台相关的问题了。
#elif   defined(_M_IX86)
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )


这里对应的系统情况下,默认对齐取得是int.所以这也就解释了另外一个问题。(如下例)

以下是一个简单的程序片段

int ADD(char a,...)
{

int val;
cout << &val << endl;

int main()
{
int a = 'a';
cout << &a << endl;
char res = ADD('a','b','b','b','a',-1);

这个问题就是说,对于用到可变参数的地方,参数传递是按照每个参数分别计算字节对齐((1补齐为4)+4=8),而后压栈的,而非一起处理字节对齐而压栈(此情形时应当差距4(1+1--》补齐为4)字节).
//
注:这个真心有点坑。原来不能直接对于图片进行复制粘贴进来,搞得我重新补充/哭



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值