我们在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 System类

    java 不支持 全局方法 和 变量, system 类 中所有成员都是静态的, 而要引用这些变量和方法,可直接system作为前缀,

  2. iPhone 被同步到 Mac上后 如果不希望更新到Mac上在哪里删除?

    前往文件夹   /Users/用户名/Library/Application Support/MobileSync  直接删除  就行了(同时要倾倒废纸篓). 目前iPhone链接Mac 后  不让 ...

  3. 【Oracle】windows默认共享的打开和关闭?

    Windows启动时都会默认打开admin$ ipc$ 和每个盘符的共享,对于不必要的默认共享,一般都会把它取消掉,可当又需要打开此默认共享时,又该从哪里设置呢,一般来说有两个地方,MSDOS命令和计 ...

  4. 【HDOJ】1510 White Rectangles

    这个题目很好,变形的题目也很多.简单DP. /* 1510 */ #include <cstdio> #include <cstring> #include <cstdl ...

  5. POJ2240 Arbitrage(最短路)

    题目链接. 题意: 根据汇率可以将一种金币换成其他的金币,求最后能否赚到比原来更多的金币. 分析: 最短路的求法,用floyd. #include <iostream> #include ...

  6. POJ2524 Ubiquitous Religions(并查集)

    题目链接. 分析: 给定 n 个点和 m 条无项边,求连通分量的数量.用并查集很简单. #include <iostream> #include <cstdio> #inclu ...

  7. 32位vs2010的项目如何在64位系统上运行

    64位注册 1. 关闭Visual Studio.2. 在Visual Studio Tools目录,以管理员身份运行Visual Studio Command Prompt (2010),[注:这个 ...

  8. 关于unsigned int和int的加法

    补码(two's complement) 在计算机系统中,数值一律用补码来表示和存储.原因在于,使用补码,可以将符号位和数值域统一处理:同时,加法和减法也可以统一处理.此外,补码与原码相互转换,其运算 ...

  9. [Java] LinkedList / Queue - 源代码学习笔记

    简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口 ...

  10. spring中@value注解需要注意

    首先,@value需要参数,这里参数可以是两种形式:@Value("#{configProperties['t1.msgname']}")或者@Value("${t1.m ...