一、在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表。
void fun(...);
void fun(parm_list,...);
#include <stdio.h>
void fun(int n,...)
{
int *temp=&n;
temp++;
for(int i=;i<n;i++)
{
printf("%d\n",*temp);
temp++;
}
}
int main()
{
int a=,b=,c=,d=;
fun(,a,b,c,d);
return ;
}

二、使用va_start,va_arg,va_end

#include <stdio.h>
#include <stdarg.h>
void fun(int n,...) //变参函数,C++里也有
{
int temp,i;
va_list ap; //step1:va_list类型数据可以保存函数的所有参数,做为一个列表一样保存 va_start(ap,n); //step2:对ap 进行初始化,让ap指向可变参数表里面的第一个参数(最后一个固定参数后的可变参数)
for(i=;i<n;i++)
{
temp=va_arg(ap,int); //step3:把 ap 的位置指向变参表的下一个变量位置
printf("%d\n",temp);
}
va_end(ap); //step4:关闭ap指针
}
int main()
{
int a=,b=,c=,d=;
fun(,a,b,c,d);
return ;
}

<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是成对出现。
 
三、函数参数传递原理
这里要知道两个事情:
     ⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
    
(2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。  
  栈底 高地址
| .......
| 函数返回地址
| .......
| 函数最后一个参数
| ....
| 函数第一个可变参数 <--va_start后ap指向
| 函数最后一个固定参数
| 函数第一个固定参数
栈顶 低地址
所使用到的宏:
     void va_start( va_list arg_ptr, prev_param );
     type va_arg( va_list arg_ptr, type );
     void va_end( va_list arg_ptr );
宏解释:
    typedef char *   va_list;
     #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
     #define va_start(ap,v)   ( ap = (va_list)&v + _INTSIZEOF(v) )
     #define va_arg(ap,t)     ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
     #define va_end(ap)       ( ap = (va_list)0 )
1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。
 
4、va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
因此,现在再来看va_arg()的实现就应该心中有数了:

     #define va_arg(ap,t)     ( *(t *)((ap += _INTSIZEOF(t)) -
_INTSIZEOF(t)) )
这个宏做了两个事情,
    ①ap指向下一个参数:ap +=
_INTSIZEOF(t)
    ②表达式取得本参数的值:先是让ap指向下一个参数:ap +=
_INTSIZEOF(t),然后在减去_INTSIZEOF(t),使得表达式结果为ap之前的值,即当前需要得到的参数的地址,强制转换成指向此参数的

类型的指针,然后用*取值
 
int main(void)
{
int a=,b=;
b=((a+=)-);
printf("a:%d,b:%d\n",a,b);
b=((a+=)-);
printf("a:%d,b:%d",a,b);
return ;
}

5、va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
 

va_start,va_arg,va_end的使用的更多相关文章

  1. va_list/va_start/va_arg/va_end深入分析【转】

    转自:http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html va_list/va_start/va_arg/va_end ...

  2. C++省略参数(va_list va_start va_arg va_end)的简单应用

    原文参考自:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html #include <iostream> #in ...

  3. va_list/va_start/va_arg/va_end深入分析

    http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html

  4. va_start(),va_end()函数应用【转】

    转自:http://www.cnblogs.com/gogly/articles/2416833.html 原理解释: VA_LIST 是在C语言中解决变参问题的一组宏,在<stdarg.h&g ...

  5. va_start(),va_end()函数应用

    原理解释: VA_LIST 是在C语言中解决变参问题的一组宏,在<stdarg.h>头文件下. VA_LIST的用法:            (1)首先在函数里定义一具VA_LIST型的变 ...

  6. va_list、va_start和va_end使用

    我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1.硬件平台的不同 2.编译器的不同,所以定义的宏也有所不同. 在ANSI C中,这些宏的定义位于stdar ...

  7. va_start和va_end使用详解

    本文主要介绍va_start和va_end的使用及原理. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 ...

  8. [转载]va_start和va_end使用详解

    va_start和va_end使用详解 原文地址:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_s ...

  9. [转载]C/C++可变参数之va_start和va_end使用详解

    本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两 ...

随机推荐

  1. Python基础 语法特别注意笔记(和Java相比)

    Python变量和数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ...

  2. Java异常处理示例

    翻译人员: 铁锚 翻译日期: 2013年11月22日 原文链接: Java Exception Handling Example 本文中有两个示例, 第一个演示了所有调用其他方法的地方,都必须处理被调 ...

  3. (四十一)数据持久化的NSCoding实现 -实现普通对象的存取

    NSCoding可以用与存取一般的类对象,需要类成为NSCoding的代理,并且实现编码和解码方法. 假设类Person有name和age两个属性,应该这样设置类: .h文件: #import < ...

  4. 如何设计一个web容器

    开发一个web容器涉及很多不同方面不同层面的技术,例如通信层的知识,程序语言层面的知识等等,且一个可用的web容器是一个比较庞大的系统,要说清楚需要很长的篇幅,本文旨在介绍如何设计一个web容器,只探 ...

  5. 《java入门第一季》之类(Object类)

    package cn.itcast_01; /* * Object:类 Object 是类层次结构的根类.每个类都使用 Object 作为超类. * 每个类都直接或者间接的继承自Object类. * ...

  6. Android For JNI(五)——C语言多级指针,结构体,联合体,枚举,自定义类型

    Android For JNI(五)--C语言多级指针,结构体,联合体,枚举,自定义类型 我们的C已经渐渐的步入正轨了,基础过去之后,就是我们的NDK和JNI实战了 一.多级指针 指针的概念我们在前面 ...

  7. android数据保存之greendao

    有时我们的数据属于保存到数据库,对于Android应用和IOS应用,我们一般都会使用SQLite这个嵌入式的数据库作为我们保存数据的工具.由于我们直接操作数据库比较麻烦,而且管理起来也非常的麻烦,以前 ...

  8. FFMPEG结构体分析:AVCodec

    注:写了一系列的结构体的分析的文章,在这里列一个列表: FFMPEG结构体分析:AVFrame FFMPEG结构体分析:AVFormatContext FFMPEG结构体分析:AVCodecConte ...

  9. FFMPEG结构体分析:AVFormatContext

    注:写了一系列的结构体的分析的文章,在这里列一个列表: FFMPEG结构体分析:AVFrameFFMPEG结构体分析:AVFormatContextFFMPEG结构体分析:AVCodecContext ...

  10. *** non-numeric second argument to `wordlist' function: ''. Stop错误解决办法

    PS: 解决办法搜集自:stackoverflow website:http://stackoverflow.com/questions/5677178/ndk-gdb-fails-with-mess ...