一. 调用形式

void foo(int argv1, char argv2, …)
在参数表的末尾给出省略号,表明这个函数的参数是可变的

二. 工作原理

进程在调用函数时,会将函数参数压入用户栈,压入的顺序是从参数表右端开始,从右至左的压栈顺序支持了可变参数的实现。左边的参数在低地址,右边的参数在高地址。进入函数后,以左边的参数为线索,可透过指针依次访问右边省略掉的参数。

可变参数的实现一定会涉及到三个函数和一个变量,宏定义在stdarg.h中
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);

实现正确的函数调用,可遵循以下步骤:
1.定义一个va_list类型变量,变量名为ap
2.使用函数va_start(ap, last)对ap进行初始化,第一个参数是ap,第二个参数是省略号前一个变量,在有多个变量的情况下需注意。
3.使用va_arg(ap, type)获取省略号中的可变参数,该可变参数的类型应为type,并将指针指向下一个可变参数
4.在获取完所有的可变参数后(需要小心指针越界),用函数va_end(ap)关闭ap。

下面是glibc中函数printf()的实现源码

//glibc 2.14.1
/* Write formatted output to stdout from the format string FORMAT.  */
/* VARARGS1 */
int
__printf (const char *format, ...)
{va_list arg;int done;va_start (arg, format);done = vfprintf (stdout, format, arg);va_end (arg);return done;
}

三. 例子

我们可以自己实现简易版的myprintf()

#include <stdio.h>
#include <stdarg.h>//如果在嵌入式系统中,putchar(ch) 可换成串口输出函数
#define console_print(ch)    putchar(ch)void    myprintf(char* fmt, ...);
void    printch(char ch);
void    printdec(int dec);
void    printflt(double flt);
void    printbin(int bin);
void    printhex(int hex);
void    printstr(char* str);int main(void)
{myprintf("print: %c\n", 'c');myprintf("print: %d\n", 1234567);myprintf("print: %f\n", 1234567.1234567);myprintf("print: %s\n", "string test");myprintf("print: %b\n", 0x12345ff);myprintf("print: %x\n", 0xabcdef);myprintf("print: %%\n");return 0;
}void myprintf(char* fmt, ...)
{double vargflt = 0;int vargint = 0;char* vargpch = NULL;char vargch = 0;char* pfmt = NULL;va_list vp;va_start(vp, fmt);pfmt = fmt;while(*pfmt){if(*pfmt == '%'){switch(*(++pfmt)){case 'c':vargch = va_arg(vp, int); printch(vargch);break;case 'd':case 'i':vargint = va_arg(vp, int);printdec(vargint);break;case 'f':vargflt = va_arg(vp, double);printflt(vargflt);break;case 's':vargpch = va_arg(vp, char*);printstr(vargpch);break;case 'b':case 'B':vargint = va_arg(vp, int);printbin(vargint);break;case 'x':case 'X':vargint = va_arg(vp, int);printhex(vargint);break;case '%':printch('%');break;default:break;}pfmt++;}else{printch(*pfmt++);}}va_end(vp);
}void printch(char ch)
{console_print(ch);
}void printdec(int dec)
{if(dec==0){return;}printdec(dec/10);printch( (char)(dec%10 + '0'));
}void printflt(double flt)
{int icnt = 0;int tmpint = 0;tmpint = (int)flt;printdec(tmpint);printch('.');flt = flt - tmpint;tmpint = (int)(flt * 1000000);printdec(tmpint);
}void printstr(char* str)
{while(*str){printch(*str++);}
}void printbin(int bin)
{if(bin == 0){printstr("0b");return;}printbin(bin/2);printch( (char)(bin%2 + '0'));
}void printhex(int hex)
{if(hex==0){printstr("0x");return;}printhex(hex/16);if(hex < 10){printch((char)(hex%16 + '0'));}else{printch((char)(hex%16 - 10 + 'a' ));}
}

四. 注意事项

1. 可变参数’…’里面的char会被提升为int,float会被提升为double。如果我们将va_arg(ap, int)改为va_arg(ap, char),系统会给出一个警告。详情可见[3].

Waring: 'char' is promoted to 'int' when passed to through '...'

2. 一定要使用arg_end(),可提高程序的移植性和健壮性。详情可见[4].

【Reference】
1.myprintf实现 http://blog.csdn.net/xfeng88/article/details/6695848
2.stdarg相关函数 http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
3.va_arg不可接受的类型 http://www.cppblog.com/ownwaterloo/archive/2009/04/21/unacceptable_type_in_va_arg.html
4.va_end是必须的吗 http://www.cppblog.com/ownwaterloo/archive/2009/04/21/is_va_end_necessary.html

转载于:https://www.cnblogs.com/season-peng/p/6713490.html

可变参数函数——以printf为例子相关推荐

  1. C语言可变参数函数(printf/scanf)

    C 语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function).这种函数需要固定数量的强制参数(mandatory argument),后面是数量可变的可选参数(opti ...

  2. 可变参数函数的一个简单例子

    这里只是简单的描述一下C语言中如何使用参数数量可变的函数,至于stdarg.h中关键宏如何实现以及编译器如何处理这些更底层的东西,以后再说. 我们知道,C语言中函数体包含三个要素:返回值类型.函数体. ...

  3. printf以及可变参数函数讲解(转载)

    printf以及可变参数函数的讲解(转载自 谁不小心的) 添加链接描述 printf以及可变参数函数的讲解 转载自 谁不小心的 链接:https://blog.csdn.net/trochiluses ...

  4. 解析可变参数函数的实现原理(printf,scanf)

    From: http://hi.baidu.com/huifeng00/blog/item/085e8bd198f46ed3a8ec9a0b.html 学习C的语言的时候,肯定接触到标准输出和标准输入 ...

  5. 从printf谈可变参数函数的实现

    作者:戎亚新 摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒.printf这种对参数个数和参数类型的 ...

  6. printf 函数使用 可变参数函数实现原理

    一. Printf 和scanf 函数格式 Printf 和 scanf 家族函数都属于可变参数函数(variadic function).这种函数需要固定数量的强制参数,后面是数量可变的可选参数. ...

  7. 关于C语言可变参数函数的一些研究和总结

    可变参数函数是指函数参数的个数.类型等是不固定的,需要在用户调用过程中,根据实际传入的参数来确定其类型.个数等信息.例如:可变参数函数printf可谓是在C开发过程中使用最多的标准输出库函数之一,因此 ...

  8. C语言可变参数函数_初探

    一.什么是可变参数函数 C语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function).这种函数需要固定数量的强制参数,后面是数量可变的可选参数. 其中,强制参数必须至少一 ...

  9. 揭密X86架构C可变参数函数实现原理

    前两天公司论坛有同事在问C语言可变参数函数中的va_start,va_arg 和 va_end 这些接口怎么实现的?我毫不犹豫翻开箱底,将多年前前(算算有十年了)写的文章「亲密接触C可变参数函数」发给 ...

最新文章

  1. Spring boot重定向请求
  2. ASP.NET Core Web API
  3. python get post请求_使用python封装get+post请求
  4. php 伪静态规则,在线将Apache Rewrite Rules伪静态规则转换为Nginx Rewrite伪静态规则...
  5. 计算机应用技术自创ppt,教师必备:超好用的课件制作工具
  6. Docker安装ActiveMQ(docker-compose.yml)
  7. 重新安装NVIDIA显卡驱动
  8. httpconduit请求https报错_为什么重复的GET请求变慢了?
  9. 数据库SQL语句之外键
  10. 基于全网最棒的Vue教学视频(尚硅谷张天禹老师)整理出的最详细的Vue指令笔记
  11. vue项目引入三方字体
  12. OpenGauss/MogDB调用C FUNCTION 范例
  13. 尤大大(尤雨溪)的年度总结、预期
  14. 同步通信和异步通信的爱恨情仇
  15. 操作系统面试题(转载)
  16. 带着问题读源码-soul(2021-01-16)
  17. win10 ODBC数据源32位没有Microsoft Access Driver(*.mdb,*accdb)
  18. 1024程序员吐槽大会,看完头发都笑掉了
  19. 安装python最新版需要卸载老版嘛_python的安装与版本共存与卸载
  20. 打破单片机开发模式--胶水语言(JavaScript)

热门文章

  1. html radio 默认图片替换_用纯CSS改变html radio/checkbox默认背景颜色样式
  2. 跟驰理论 matlab,[自然科学]第4章 跟驰理论.ppt
  3. Centos7 ifconfig这个命令没找到的解决方法
  4. dell服务器r730老自动重启_Dell R730服务器安装windows server 2008 R2蓝屏问题
  5. python mro c3_Python的MRO以及C3线性化算法
  6. 首都师范大学计算机科学与技术考研分数线,2017考研:计算机科学与技术专业考研院校推荐之首都师范...
  7. 系统学习NLP(十七)--文本相似度
  8. 数字信号处理--7.5--FIR数字滤波器
  9. W Zong / A Robust Open-source Algorithm to Detect Onset and Duration of QRS Complexes
  10. python 矩量法_矩量法:β二项分布