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

  除了一个格式化字符串之外还可以输入多个可变参量,如:

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

  格式化字符串的判断本章暂且不论,下面分析一下可变参数的实现细节。

一、简单实例

int simple(int num,...)
{
int i, result=;
va_list vl; //va_list指针,用于va_start取可变参数,为char*
va_start(vl,num); //取得可变参数列表中的第一个值
printf("num:%d, vl:%d\n",num,*vl);
for (i = ; i < (num-); i++)//这里num表示可变参数列表中有多少个参数
{
result = va_arg(vl, int);//这里把vl往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数)
printf("in for result:%d, *vl:%d\n", result, *vl);//这里打印下,可以看出,vl总是指向result后面的那个参数
}
va_end(vl);//结束标志
return result; }
int main(int argc, char **argv)
{
int num = argc;
int i = ;
simple(,,,,,);
return ;
}

  运行结果如下:

num:, vl:
in for result:, *vl:
in for result:, *vl:
in for result:, *vl:
in for result:, *vl:5

二、相关API

  可变参数列表的实现是由几个宏组成的,在文件include/stdarg.h中:

  va_list  定义某个变量,内核中的定义:

typedef char *va_list;//字符指针类型 

  va_start(ap, type)   开始获取可变参数列表中的第一个参数(...里面的第一个),也就是跳过第一个参数(即num):

#ifndef __sparc__
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))//ap指向下一个参数,lastarg不变
#else
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), \
AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) //跳过下第一个参数,指向第二个参数内存地址
#endif //对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - ) / sizeof (int)) * sizeof (int))

  va_arg(args, int)  循环获取到可变参数列表中的参数,args指向下一个参数地址,返回的则是当前参数地址

//  first=va_arg(args,int)
#define va_arg(AP, TYPE) \//ap指向下一个类型的参数
(AP += __va_rounded_size (TYPE), \//返回ap - sizeof(type)参数,即前一个参数
*((TYPE *) (AP - __va_rounded_size (TYPE)))) //对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - ) / sizeof (int)) * sizeof (int))

  最后一个va_end(ap)结束标志,可能只是在程序中作为一个可变参数列表的结束标志而已(stdarg.h里面只是仅仅定义了下,没有实现的代码部分)。

三、注意事项

  因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型.有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数printf是从固定参数format字符串来分析出参数的类型,再调用va_arg的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利

  如将simple可变参数该成char型指针,若存在空指针在会产生coredump

void simple(int i, ...)
{
  va_list arg_ptr;
  char *s=NULL;   va_start(arg_ptr, i);
  s=va_arg(arg_ptr, char*);
  va_end(arg_ptr);
  printf("%d %s\n", i, s);
  return;
}

  可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出错,但错误却是难以发现,不利于我们写出高质量的程序。

C语言中可变参数的使用的更多相关文章

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

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

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

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

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

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

  4. C语言中可变参数的原理——printf()函数

    函数原型: int printf(const char *format[,argument]...) 返 回 值: 成功则返回实际输出的字符数,失败返回-1. 函数说明: 使用过C语言的人所再熟悉不过 ...

  5. c 中可变参数的实现

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

  6. c语言中函数参数入栈的顺序是什么?为什么

    看到面试题C语言中函数参数的入栈顺序如何? 自己不知道,边上网找资料.下面是详细解释 #include <stdio.h> void foo(int x, int y, int z){   ...

  7. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  8. C语言的可变参数在Linux(Ubuntu)与Windows下注意点

    基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...

  9. Java 中可变参数

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

随机推荐

  1. MVVM技术 - 的实现 @{}来进行 调用那个 DataBinding方法

    new Material Design 支持哭 还有 Data Binding 结束   使用DataBindign 结束 我们很方面的实现 MVVM设计模式   什么是MVVM model 呢.   ...

  2. oracle报错:ORA-01658(转自52斋347)

    在oracle里创建表,报出错:ORA-01658: 无法为表空间space中的段创建 INITIAL 区:或者: ORA-01658: unable to create INITIAL extent ...

  3. Python开发环境Wing IDE如何调试进程异常报告

    Wing IDE的调试器所报告的任何异常,都会在调试器以外的任何代码运行事件中展示出来. 通过使用Debug工具或者是Debug菜单中的Start / Continue继续调试过程的异常检测. Win ...

  4. Tomcat中配置JAVA_HOME

    一般来说我们在使用Tomcat时配置JAVA_HOME都是采用的系统环境变量直接配置,下面我提供一种新的配置思路. 这里我们使用的是免安装版的apache-tomcat-7.0.35,首先我们安装好j ...

  5. zabbix-3.4-服务监控

    服务监控 总览 服务监控(services monitoring)旨在帮助那些想要高级(业务)基础设施的监控的人.在许多情况下,我们关注的不是底层细节,比如磁盘空间不足.CPU 负载高等.我们关注的是 ...

  6. 青松云安全-WAF-1.0.655 (ubuntu 14.04 server)

    平台: Ubuntu 类型: 虚拟机镜像 软件包: web application firewall basic software security ubuntu waf 服务优惠价: 按服务商许可协 ...

  7. thinkphp分页+条件查询

    最近项目上面有一个带条件查询的分页列表,一开始form用的post,点击第二页就没有跳转成功,原因是分页是get请求,post数据链接到其他页面就会被清除. 解决办法: 1.form表单method= ...

  8. 谈谈bootstrap在实践中的应用

    bootstrap官网是http://www.bootcss.com/ bootstrap的CDN的网址是http://www.bootcdn.cn/ 在平时写的时候尽量用CDN,这样对于网站的运行效 ...

  9. HDU1195 双向BFS(或BFS)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1195 , 双向BFS或者直接BFS也可以过. 其实这道题只是单向BFS就可以过的,但是为了练算法,所以 ...

  10. IOS UIActionSheet(底部 弹出框的使用)

    UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"确定要注销?" delegate:self cancel ...