一、在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. 敏捷测试(6)--基于story的敏捷基础知识

    基于story的敏捷基础知识----需求管理(三) (3)每日站会 站会的目的有三个: (1)周知进度 仅从用户故事和任务的层面周知进度,任务进度只有两种状态:完成或未完成(完成百分比). (2)周知 ...

  2. kafka原理简介并且与RabbitMQ的选择

    kafka原理简介并且与RabbitMQ的选择 kafka原理简介,rabbitMQ介绍,大致说一下区别 Kafka是由LinkedIn开发的一个分布式的消息系统,使用Scala编写,它以可水平扩展和 ...

  3. Android 免Root实现Apk静默安装,覆盖兼容市场主流的98%的机型

    地址:http://blog.csdn.net/sk719887916/article/details/46746991 作者: skay 最近在做apk自我静默更新,在获取内置情况下,或者已root ...

  4. 【Qt编程】Qt 小时钟

    Hello World! 学习编程语言的最简单最经典的小程序,当然Qt也不例外.在学习画图时,我觉得写个时钟小程序也是个比较好的开始.在之前的<Matlab及Java小时>一文中,我也从写 ...

  5. Google性能工程师Ilya Grigorik谈HTTP/2

    via:http://www.infoq.com/cn/news/2014/11/http2-develop HTTP/2,也就是超文本传输协议第2版,是下一代HTTP协议.该版本是自1999年HTM ...

  6. 停止预览时调用Camera.release(), 出现Method called after release()异常问题原因及解决办法

    如下代码: private void stopPreview() { Log.w(TAG, "stopPreview(), _isPreviewing = " + _isPrevi ...

  7. 苹果新的编程语言 Swift 语言进阶(十六)--泛型

    泛型允许你定义一个宽松.可重用的函数或者类型,使用泛型能够避免代码的重复,也能以更清楚和抽象的方式来表达程序的意图. 泛型是Swift语言提供的强大功能之一,Swift提供的许多标准库都使用了泛型来创 ...

  8. Linux网络设置(第二版) --Linux网络设置

    Linux网络设置 --网络配置文件与命令 个 附- 服务程序可以不使用固定端口,但是一般对外公开的WebServer不会改变端口,但是像SSH一般推荐更改,可以回避扫描 nmap [IP地址] #扫 ...

  9. nasm预处理器(3)

    nasm提供一个限定符.nolist,可以包含它到一个宏定义中,这样该宏就不会在列表文件中被展开:限定符 .nolist直接放到参数后面: %macro foo 1.nolist 条件汇编 和C预处理 ...

  10. lpad函数

    函数介绍 lpad函数是Oracle数据库函数,lpad函数从左边对字符串使用指定的字符进行填充.从其字面意思也可以理解,l是left的简写,pad是填充的意思,所以lpad就是从左边填充的意思. 2 ...