在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. 不该被忽视的CoreJava细节(四)

    令人纳闷的数组初始化细节 这个细节问题我很久以前就想深入研究一下,但是一直没有能够抽出时间,借这系列文章的东风,尽量解决掉这个"心头病". 下面以一维int数组为例,对数组初始化方 ...

  2. IIS重叠回收

    在IIS应用程序池的高级设置中,有一个“禁用重叠回收”属性,默认值是False. 重叠回收(Overlapped Recycling),指的是当回收的时候,原来的进程继续处理正在处理的请求,同时一个新 ...

  3. SQLServer从其他表获取的数据更新该表的一部分

    在网上常见的是update  a  set  username  =  username  FROM b  on a.userid=b.userid,该更新语句是对a表中所有行进行更新.如果只更新一部 ...

  4. JS整数验证

    整数验证 方法1 function ValidatInteger(obj) { var reg = /^[1-9]\d*$/ if (!reg.test($(obj).val())) { $(obj) ...

  5. linux命令 ——目录

    开始详细系统的学习linux常用命令,坚持每天一个命令,所以这个系列为每天一个linux命令.学习的主要参考资料为: 1.<鸟哥的linux私房菜> 2.http://codingstan ...

  6. linux 命令——11 nl (转)

    nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等 ...

  7. Extjs4.1+desktop+SSH2 搭建环境 项目能跑起来

    linux开发感觉可能就是日常办公的时候,用别的软件会有问题,java开发还是没什么区别的,换回window开发: push 它: 每次看到右上那红红的叉,我还以为又出错了: 这个项目用resin,下 ...

  8. kubernetes-控制器Deployment和DaemonSet(八)

    Pod与controllers的关系 •controllers:在集群上管理和运行容器的对象•通过label-selector相关联•Pod通过控制器实现应用的运维,如伸缩,升级等 控制器又称工作负载 ...

  9. cuda api查询问题

    在查询CUDA运行时API的时候,我用360极速浏览器的时候搜索结果一直不出来,但是用火狐的话就很流畅,所以建议大家在开发时还是用火狐浏览器.

  10. java编程基础——栈压入和弹出序列

    题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压 ...