可变参数函数实现

va_list,va_start,va_arg,va_end

va可变参数意思,variable-argument。

1. 头文件及实现

linux中定义在gcc头文件中,stdarg.h中。

  #ifndef __GNUC_VA_LIST

  #define __GNUC_VA_LIST

  typedef __builtin_va_list __gnuc_va_list;

  #endif

  /* Define the standard macros for the user,

 47    if this invocation was from the user program.  */

  #ifdef _STDARG_H

  #define va_start(v,l)   __builtin_va_start(v,l)

  #define va_end(v)   __builtin_va_end(v)

  #define va_arg(v,l) __builtin_va_arg(v,l)

加builtin前缀的都是编译器内置函数,在机器上找不到源代码。

GCC provides a large number of built-in functions other than the ones mentioned above. Some of these are for internal use in the processing of exceptions or variable-length argument lists and are not documented here because they may change from time to time; we do not recommend general use of these functions.

The remaining functions are provided for optimization purposes.

GCC includes built-in versions of many of the functions in the standard C library. These functions come in two forms: one whose names start with the __builtin_ prefix, and the other without. Both forms have the same type (including prototype), the same address (when their address is taken), and the same meaning as the C library functions even if you specify the -fno-builtin option see C Dialect Options). Many of these functions are only optimized in certain cases; if they are not optimized in a particular case, a call to the library function is emitted.

在VS2008上的一种实现:

   :  ///stdarg.h

   :  #define va_start _crt_va_start

   :  #define va_arg _crt_va_arg

   :  #define va_end _crt_va_end

   :  

   :  ///vadefs.h

   :  #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )

   :  typedef char *  va_list;

   :  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

  :  #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 )

其实际就是利用函数参数入栈的顺序来计算参数地址,从而获知参数值。

函数参数是以数据结构:栈的形式存取,从右至左入栈。

  首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
  那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。

2. 应用

一种实现可查到的实现:

typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );

va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。

3. 使用总结

1)标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。

2)在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志-- printf函数就是用这个办法。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.

无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。

3)实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
      ①函数栈的生长方向
      ②参数的入栈顺序
      ③CPU的对齐方式
      ④内存地址的表达方式
    结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。

4)取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。
  可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.

4. 一个示例

对printf一层封装:

void app_printf(int log_level, const char *format, …)

{

va_list ap;

if(log_level <= current_log_level)

{

    if(log_level == KDUMMY)

         return;

    va_start(ap, format);

    vprintf(format, ap);

    va_end(ap);

}

}

注:vprintf以va_list为参数打印输出到标准输出。

The functions printf() and vprintf() write output to stdout, the standard output stream。

int vprintf(const char *format, va_list ap);

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h> void foo(char *fmt, ...)
{
va_list ap;
int d;
char c, *s; va_start(ap, fmt);
while(*fmt){
switch(*fmt++){
case 's':
s = va_arg(ap, char *);
printf("string %s\n", s);
break;
case 'd':
d = va_arg(ap, int);
printf("int %d\n", d);
break;
case 'c':
/* need a cast here since va_arg only
takes fully promoted types */
c = (char) va_arg(ap, int);
printf("char %c\n", c);
break;
default :
break;
}
}
va_end(ap);
} int main(int argc, char **argv)
{
foo("dscs", , "mouse", 'A', "end");
printf("--------------------\n");
printf("%d%s%c%s\n", , "mouse", 'A', "end"); return ;
}

执行结果:

~$gcc valist.c -Wall
~$./a.out
int
string mouse
char A
string end
--------------------
100mouseAend

参考:

    1. http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html
    2. http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
    3. http://blog.csdn.net/edonlii/article/details/8497704

va_list可变参数的更多相关文章

  1. 【转载】va_list 可变参数 简介 va_copy vprintf

    [说明]本文转载自 smart 的文章 http://blog.sina.com.cn/s/blog_590be5290100qhxr.html  及百度百科 va_list是一个宏,由va_star ...

  2. 如何把va_list可变参数传送到下一级函数中(如传送到printf)

    最近我在一个LCD上想实现打印格式化字符串的功能,实现这样的功能可有两种方式: 一 最直接的就是自己去解析类似于printf功能的一个函数: 二 比较简单的方法是使用已有的sprintf功能,把格式化 ...

  3. C++可变参数列表处理宏va_list、va_start、va_end的使用

      VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /* ...

  4. 【转】C++可变参数列表处理宏va_list、va_start、va_end的使用

    VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /*po ...

  5. 可变参数以及stdcall

    void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); #define EV_CHECK_FMT(a,b) __attribute__((f ...

  6. 深入C语言可变参数(va_arg,va_list,va_start,va_end,_INTSIZEOF)

    一.什么是可变参数 在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为: int printf(const char* format,…),int ...

  7. ios 可变参数(va_list,va_start,va_end)

    例如:UIAlertView的init方法中的otherButtonTitles:(NSString *)otherButtonTitles, ...等多个可变参数. ios实现传递不定长的多个参数的 ...

  8. C语言利用va_list、va_start、va_end、va_arg宏定义可变参数的函数

    在定义可变参数的函数之前,先来理解一下函数参数的传递原理: 1.函数参数是以栈这种数据结构来存取的,在函数参数列表中,从右至左依次入栈. 2.参数的内存存放格式:参数的内存地址存放在内存的堆栈段中,在 ...

  9. C语言可变参数va_list

    一.什么是可变参数 在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为: int printf(const char* format,-) int ...

随机推荐

  1. Session集中式管理

          Asp.net Session集中式管理主要有StateServer(状态服务器).Sqlserver(数据库服务器).自定义(如Redis缓存服务器)等,本文主要介绍StateServe ...

  2. [Git] 关于refs/for/ 和refs/heads/

    转载自: http://lishicongli.blog.163.com/blog/static/146825902013213439500/ 1.     这个不是git的规则,而是gerrit的规 ...

  3. ylbtech-dbs-m-YinTai(银泰网)

    ylbtech-dbs:ylbtech-dbs-m-YinTai(银泰网) -- =============================================-- DatabaseNam ...

  4. DEDECMS之0day入侵总结

    1.查看dedecms最后升级版本:http://xxx.com/data/admin/ver.txt 2.利用网上公开之0day进行对应版本之入侵 ps:dedecms默认CMS后台:http:// ...

  5. centos7使用samba共享文件

    samba是一款可以让linux和windows下共享文件的常用的一款软件 如何在centos7中使用和配置samba 首先先安装 sudo yum install samba 下载完成查看rpm - ...

  6. D3.js系列——动态效果和Update、Enter、Exit的理解

    一.动态效果 D3 支持制作动态的图表.有时候,图表的变化需要缓慢的发生,以便于让用户看清楚变化的过程,也能给用户不小的友好感. 1.什么是动态效果 前面制作的图表是一蹴而就地出现,然后绘制完成后不再 ...

  7. char p[]与char *p的区别

    #include <iostream> using namespace std; int main(void) { char *pp = "abc";//*pp指向的是 ...

  8. 警惕rapidxml的陷阱(二):在Android上默认内存池分配数组过大,容易导致栈溢出

    上一篇随笔中提到了,rapidxml在每个xml对象中维护了一个内存池,自己管理变量的生存周期.看起来很好,但我们在实际使用中还是出现了问题. 项目中我们的模块很快写好了,在windows和linux ...

  9. 无password身份验证:安全、简单且部署高速

    Passwordless authentication: Secure, simple, and fast to deploy [编者按]本文作者为 Florian Heinemann 与 Rober ...

  10. ThreadLocal的简单使用(读书笔记)

         从ThreadLocal的名字上可以看到,这是一个线程局部变量,也就是说,只有当前线程可以访问,既然是只有当前线程可以访问的数据,自然是线程安全的. public class ThreadL ...