在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. hibernate课程 初探单表映射2-6 session详解(下)

    本节主要内容: 1 介绍了getCurrentSession和opensession的区别 2 demo:通过打印比较两个session是否相同,验证两个session是否是同一session 3 d ...

  2. Check-Point-Security-Gateway-BYOL-R77.30-041.161

    平台: CentOS 类型: 虚拟机镜像 软件包: checkpoint redhat smartconsole basic software security vfw 服务优惠价: 按服务商许可协议 ...

  3. Visual Studio 更改护眼颜色

  4. js中arr.sort的用法

    sort(sortfunction)为JS的数组对象(Array)的一个方法,提供排序功能 参数 sortFunction 为可选项,是用来确定排序原则的js函数, 这个函数有两个参数,分别代表每次排 ...

  5. IOS 控件器的创建方式(ViewController)

    ● 控制器常见的创建方式有以下几种 ➢ 通过storyboard创建 ➢ 直接创建 NJViewController *nj = [[NJViewController alloc] init]; ➢ ...

  6. 集成Ehcache用来缓存表以后,怎么设置缓存刷新时间

    问答 集成Ehcache用来缓存表以后,怎么设置缓存刷新时间  发布于 217天前  作者 老司机  93 次浏览  复制  上一个帖子  下一个帖子  标签: 无 集成Ehcache用来缓存表以后, ...

  7. Codeforces Round #324 (Div. 2) A B C D E

    A,水题不多说. #include<bits/stdc++.h> using namespace std; //#define LOCAL int main() { #ifdef LOCA ...

  8. Android(java)学习笔记74:ListViewProject案例(ListView + ArrayAdapter)

    1. 首先是MainActivity.java文件,如下: package com.himi.lv1; import java.util.ArrayList; import java.util.Lis ...

  9. vuejs课程简介及框架简介

    vuejs准备知识: 1.前端开发基础 html css js 2.前端模块化基础 3.对es6有初步的了解   vuejs是一种轻量级的MVM框架,他吸收了react和angular的优点,强调re ...

  10. yield 生成器的运行机制

    yield 生成器的运行机制 当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把 yield 的参数给你,之后生成器就不会往下继续运行. 当你问他要下一个数时,他会从上次的状态 ...