C语言的变参的研究

      最近,老遇到C语言中使用变参的问题,一直不明白怎么回事,就稍许研究了一下,感觉挺有收获的,于是就有了本文。

      函数的变参实际上就是:C语言利用调用栈而提供的一种参数传递的机制

一、调用栈

      我们知道C语言的调用约定为__cdecl,它的特点为:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。要了解它的确切含义,就先看一下函数调用是发生了什么,假设有如下的程序:

   1: #include <stdio.h>
   2: #include <stdarg.h>
   3:  
   4: void foo(int count, ...)
   5: {
   6:     va_list args;
   7:     int t = 0;
   8:     int i = 0;
   9:  
  10:     va_start(args, i);
  11:  
  12:     for (i = 0; i < count; ++i) {
  13:         t = va_arg(args, int);
  14:         printf("%d", t);
  15:     }
  16:  
  17:     va_end(args);
  18: }
  19:  
  20: int main(int argc, char * argv[])
  21: {
  22:     foo(5, 1, 2, 3, 4, 5);
  23:     return 0;
  24: }
  25:  

      在这段代码中,定义了一个带变参的函数foo,第一个参数为变参的个数,然后在main函数中对其进行了调用。当我们在调用foo函数的时候,按照调用约定,栈如下图所示:

%U00DB_A[97R@GL1T]DJ3PW

      我们知道,栈是向低地址方向生长的,当调用foo时,先由右向左将参数如栈,最后将返回地址如栈,这个结构只有调用者知道,这是调用约定规定的。

二、被调用函数内部

      进入foo函数以后,foo函数内部是对上图栈的结构一无所知,准确的来说是变参的部分一无所知。它只知道它被调用了,有一个参数为变参的个数。为了获取这些变参,我们可以通过宏va_start来获取参数列表,这个宏有一个参数指定了第一个变参在那个参数后面。通过上图我们可以看出,要获取第一个变参的地址必之它的地址在count参数的后面

      获取每一个参数是通过宏va_arg来进行的,它需要指定每个参数的类型。从这一点我们可以看出,foo函数真的对栈上的变参情况一无所知。这一点告诉我们,foo函数内部要想主动知道变参的个数,是不可能的。当然,你可以通过其它方式获知。而且,刚好要获取上面的每个参数的首地址,只需要进行这样的计算即可:第一个变参的地址 + sizeof(参数类型)。我都怀疑,参数的入栈顺序是因为方便取才从右边的参数先如栈的

      最后一个和变参相关的宏是va_end,它实际上是简单的将变参表的指针置为空。

三、参数的获取

      在函数内部获取某一个参数,可以使用va_arg宏。但是,我遇到如下情况时会报警告:'char' is promoted to 'int' when passed through

char ch = va_arg(arglist, char);

      原因是编译器在编译是采用了内存对齐。VC上测试了一下,是按4个字节对齐的,也就是说你传进去的参数是char,则要按照int类型去除,然后再赋值给char型变量,具体如下:

char ch = va_arg(arglist, int);

      用int可以替代char short int,double替代float,double。关于这方面的说明可以参考1。

四、总结

      有了上面的知识以后,我们可以确切的回答一些和变参相关的问题了。比如,我以前考虑过的:

      1. 变参可以直接往下一层函数调用传吗?
      显然不行,因为带变参的函数内部对变参一无所知(变参的个数和类型),也就无法直接向下传递了。但是,你可以将变参表的首地址传递下去。

      2. 上面提到的获取变参的个数的问题。

      3. 可以定义变参函数为bar(…)的形式吗?
      显然不行,因为这样的话就无法获取变参表的首地址了。而且,这也是ANSI C 不允许的。

      我同样希望:这篇文章能让你想起点什么

参考资料:

      1. http://c-faq.com/varargs/float.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值