C语言中的很多函数的入参被定义为可变参数,最典型的

int printf (const char * fmt, ...)

要对其中的可变参数进行处理,就要用到va_list类型和 VA_START, VA_END, VA_ARG 宏 ,需要包含<stdarg.h>头文件

利用va族函数对不定参数进行解析的过程所示如下:

 int my_printf(const char * fmt, ...)
{
va_list struAp;
va_start(struAp, fmt); for (; *fmt; ++fmt)
{
if(*fmt != '%')
{
PUTC(*fmt);
continue;
} fmt++; switch (*fmt)
{
case 'd':
{
int i = va_arg(struAp,int);
PUTC(i);
}
break; default:
break;
}
} va_end(struAp);
}

要了解不定参数的处理方式,就要搞清楚va族函数的实现

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

/* 实质就是一个char型的指针 */

typedef char * va_list;

/* 将指针偏移一个v的长度,指向后面的地址 */

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

/* 根据参数类型,取出后面的数据,强制类型转换 */

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

/* 将指针置为NULL */

#define va_end(ap) ( ap = (va_list)0 )

通过解析fmt字符串,得到后面的参数类型和个数,根据参数类型再加上偏移量就可以找到栈中的不定参数了

函数调用和传参的过程所示如下:

将函数参数与函数调用后下一条指令的地址都压入栈中,然后跳到函数的入口地址。

例如

void func(int param1, double param2,int param3){ }

int main()
{
func(, 1.2, );
printf("Over\n"); //设指令地址为0x1234
return ;
}

执行f(3, 1.2, 4)的函数调用,进入func函数时的堆栈如下:

 
 这样,通过param1的地址就可以计算出param2与param3的地址:
 
使用不定参函数的注意事项
 
  • 在C语言中,调用一个可变参数函数时,调用者会对每个参数执行“默认实际参数提升(default argument promotions)”

——float类型的实际参数将提升到double
  ——char类型的实际参数将提升到int
  ——short类型的实际参数将提升到int

  • 在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。

——《C语言程序设计》第2版  2.7
类型转换 p36

  • 这样写肯定是不对的:

   c = va_arg(ap,char);

  因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
  c = va_arg(ap,int);

               
——《C陷阱与缺陷》p164

C语言函数可变长度参数剖析的更多相关文章

  1. C语言函数可变参数列表

    C语言允许使用可变参数列表,我们常用的printf函数即为可变参数函数,C标准库提供了stdarg.h为我们提供了这方面支持:该头文件提供了一些类型和宏来支持可变参数列表,包括类型va_list,宏v ...

  2. C语言函数不定参数实现方式

    函数如何实现不定参数: 由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,提出了指针参数来解决问题. (1)va_ ...

  3. GO 函数的参数

    一.函数 函数的参数 1.1 参数的使用 形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参. 实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参. 函数调用: ​ ...

  4. C 语言函数参数只能传指针,不能传数组

    今天被要求编写一个C/C++冒泡算法的程序,心想这还不是手到擒来的事儿,虽然最近都是用Javascript程序,很少写C/C++程序,但是好歹也用过那么多年的C语言: 首先想的是怎么让自己的代码看上去 ...

  5. Swift 1.1语言函数参数的特殊情况本地参数名外部参数名

    Swift 1.1语言函数参数的特殊情况本地参数名外部参数名 7.4  函数参数的特殊情况 声明定义有参函数时,为函数的每一个参数都定义了参数名称.根据参数名定义的形式不同,函数参数包括本地参数和外部 ...

  6. [转]深度探索C语言函数可变长参数

    转自:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html 一.基础部分 1.1 什么是可变长参数 可变长参数:顾名 ...

  7. C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)

    上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇 ...

  8. C语言中函数可变参数解析

    大多数时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数.但在某些情况下希望函数的参数个数可以根据需要确定.典型的例子有 大家熟悉的函数printf().scanf ...

  9. 【嵌入式开发】C语言 命令行参数 函数指针 gdb调试

    . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/21551397 | http://www.hanshul ...

随机推荐

  1. .NET 实现自定义ContextUser的Identity和Principal实现自定义用户信息,权限验证。

    备用收藏,http://blog.csdn.net/msdnxgh/article/details/6894885 .NET 实现自定义ContextUser的Identity和Principal 在 ...

  2. 【原创】 SharePoint Service Unavaliable

    HTTP Error 503. The service is unavailable. 一般都是application pool停止工作了,停止原因可能是Identiy里的账号过期了什么的.

  3. django rest_framework

    环境 django 1.6,rest_framework 3.3 ubuntu采用pip安装的rest_framework 按照例子一步步做下来 运行 提示filters.py第119行有错误form ...

  4. Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)

    导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html. 本文主要来讲解以下内容: ...

  5. [芯片] 3、接口技术·实验三·可编程并行接口8255A

    目录 一.实验目的和要求 二.实验原理与背景 2-1.8255A简介 2-2.8255A编程 三.实验具体的内容 3-1.8255方式0实验1 3-2.8255方式0实验2 3-3.8255方式1输出 ...

  6. centos网卡错误Device eth0 does not seem to be present

    在使用vmware及VirtualBox迁移linux系统过程中,发现部署后的linux系统无法启动网卡 报错为 Bringing up interface eth0: Device eth0 doe ...

  7. 定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间

    package java1; public class Clock { int hhh; int mmm; int sss; Clock(int h,int m,int s) { hhh=h; mmm ...

  8. Tomcat之web项目部署

    Tomcat一般用于部署JavaWeb项目. 遇到的问题 Linux操作系统中,在tomcat中部署项目时,一般只需要把项目war包:demo.war放到webapps下,然后启动tomcat即可.这 ...

  9. AndroidManifest常见的设置解析

    AndroidManifest.xml清单文件是每个Android项目所必需的,它是整个Android项目的全局描述文件.AndroidManifest.xml清单文件说明了该应用的名称.所使用的图标 ...

  10. Ruby on Rails框架开发学习

    学习地址:http://www.ixueyun.com/lessons/detail-lessonId-685.html 一.课程概述 软件开发在经历了面向过程编程的阶段,现在正大行其道的是敏捷开发, ...