我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:  
   例一: int   printf(   const   char*   format,   ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:

  printf("%d",i);    
    printf("%s",s); 
    printf("the   number   is   %d   ,string   is:%s",   i,   s);

例二:vfprintf

/*
  vfprintf 函数的原型
  int vfprintf ( FILE * stream, const char * format, va_list arg ); 
 */

void test(char* ch, ...)
{
va_list arg;
va_start(arg, ch); //初始化 arg
vfprintf(stdout, ch, arg);
va_end(arg);
} void main()
{ test("%s,%d\n", "test1", );
test("%s,%s,%d\n", "test1", "test2", );
}

C语言中可变参数函数实现原理:

C函数调用的栈结构

可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则:参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高)

void fun1(char a, int b, double c, short d) ;

int main()
{
fun1(, 5.40, "hello world");
return ;
}
a = 0x0022FF50
b = 0x0022FF54
c = 0x0022FF5C

因此,函数的所有参数是存储在线性连续的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。

我们基本可以得出这样一个结论:

c.addr = b.addr + x_sizeof(b);  /*注意:  x_sizeof !=sizeof */
 b.addr = a.addr + x_sizeof(a);

由于内存对齐,编译器在栈上压入参数时,不是一个紧挨着另一个的,编译器会根据变参的类型将其放到满足类型对齐的地址上的,这样栈上参数之间实际上可能会是有空隙的。所以此时的ap计算应该改为:ap =  (char *)ap +sizeof(int) + _INTSIZEOF (n);

为了满足代码的可移植性,C标准库在stdarg.h中提供了诸多便利以供实现变长长度参数时使用:

#define a_list char*        
#define _INTSIZEOF (n) ((sizeof(n)+ sizeof(int )-1) & ~(sizeof(int )-1))            //将 n 与 4 向上对齐
#define va_start (ap,v) (ap = (va_list)&v + _INTSIZEOF(v))      //宏初始化变量ap
#define va_arg (ap,t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))     //va_arg返回可变的参数
#define va_end (ap) (ap = (va_list)0)      //用va_end宏结束可变参数的获取
 
//一个可变参数应用的简单例子
//可变参数
int Avg(int n, ...)
{
a_list arg; //char *arg;
va_start(arg, n );//init
int sum = ;
for (int i = ; i<n; ++i)
{
sum += va_arg(arg,int );
}
va_end(arg); //arg = 0; //野指针
return sum / n ;
}
void main()
{
int avg = Avg(, , , ,,);
printf( "avg = %d\n", avg);
}

欢迎讨论~~

 
 
 

c 中可变参数的实现的更多相关文章

  1. C语言中可变参数的函数(三个点,“...”)

    C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...

  2. Java 中可变参数

    可变参数 Java 中可变参数 现在需要编写一个求和的功能,但是不知道有几个参数,在调用的时候才知道有几个参数,请问这如何实现呢? Java 给我们提供了一个 JDK 1.5 的新特性---可变参数 ...

  3. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  4. 【转】C/C++中可变参数函数的实现

    转自:http://www.cnblogs.com/cylee025/archive/2011/05/23/2054792.html 在C语言的stdarg.h头文件中提供了三个函数va_start, ...

  5. C语言中可变参数的用法

    原文地址: http://blog.csdn.net/wooin/archive/2006/04/29/697106.aspx   我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() ...

  6. 巩固java(六)----java中可变参数方法(非常实用哦)

    java提供了可变参数的方法,即方法的参数个数可以不确定,用"..."定义. import java.util.ArrayList; import java.util.List; ...

  7. C/C++中可变参数函数的实现

    在C语言的stdarg.h头文件中提供了三个函数va_start, va_end,va_arg和一个类型va_list.利用它们,我们可以很容易实现一个可变参数的函数.首先简单介绍一下这三个函数. 假 ...

  8. 18、Java中可变参数

    从JDK 1.5之后,Java允许定义形参可变的参数 例如: public void test(int a,String ... books){ for(String book:books){ Sys ...

  9. C语言中可变参数的使用

    在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式 ...

随机推荐

  1. java四种创建对象的方法

    1.用new语句创建对象,这是最常见的创建对象的方法.   2.运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance ...

  2. 各加密模式的演示(ECB,CBC)

    对于较长的明文进行加密需要进行分块加密,但是直接加密(ecb)不容易隐藏模式,用OpenCV写了个程序论证了一下 ECB 优点就是简单,可以并行计算,不会迭代误差 缺点就是隐藏不了模式 CBC 需要初 ...

  3. WIN32程序挂钩SetLastError,输出错误描述到控制台

    WIN32程序挂钩SetLastError,输出错误描述到控制台 作者:徐灵甫 一.窗口模式应用程序(GUI)启用控制台的方法为: 步骤 方法 1 启动/关闭控制台 AllocConsole()Fre ...

  4. 使用 CustomScript 扩展程序自动执行 Linux 虚拟机自定义任务

    NingKuangWSSC WS ARD 高级项目经理 您可能已经从Windows扩展程序博客中了解了针对 Windows 虚拟机的 CustomScript扩展程序,现在的好消息是,这一扩展程序也已 ...

  5. C++ BigInteger 大整数类模板(转)

    #include <deque> #include <vector> #include <iostream> #include <string> #in ...

  6. 最短路算法模板合集(Dijkstar,Dijkstar(优先队列优化), 多源最短路Floyd)

    再开始前我们先普及一下简单的图论知识 图的保存: 1.邻接矩阵. G[maxn][maxn]; 2.邻接表 邻接表我们有两种方式 (1)vector< Node > G[maxn]; 这个 ...

  7. 在Eclipse中新建Maven项目

    关于Maven的好的资料: Apache官网:http://maven.apache.org/ Apache Maven 入门篇 ( 上 ):http://www.oracle.com/technet ...

  8. SVN linux端配置

    1.create a folder:     mkdir /sandbox/svn 2.create svn repository:     svnadmin create /sandbox/svn/ ...

  9. Java并发编程:进程和线程的由来(转)

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  10. 【转】使用 NuGet 管理项目库-Phil Haack

    原文地址:https://msdn.microsoft.com/zh-cn/magazine/hh547106.aspx 无论多么努力,Microsoft 也没办法提供开发人员所需要的每一个库. 虽然 ...