C 语言精髓之变参函数
我们以 printf 这个 very 熟悉的函数为例,来分析一下变参函数。先看下 printf 函数的定义:
int printf(const char *fmt, ...)
{
int i;
int len;
/* va_list 即 char * */
va_list args;
va_start(args, fmt);
/* 内部使用了 va_arg() */
len = vsprintf(g_PCOutBuf,fmt,args);
va_end(args);
for (i = 0; i < strlen(g_PCOutBuf); i++)
{
putc(g_PCOutBuf[i]);
}
return len;
}
什么是变参函数?
可变参数函数的原型声明为 type VAFunction(type arg1, type arg2, … );
参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用 "..." 表示。固定参数和可选参数共同构成一个函数的参数列表。
printf 函数涉及了以下几个重要的宏:
typedef char * va_list;
/*
* Storage alignment properties
*/
#define _AUPBND (sizeof (acpi_native_int) - 1) //acpi_native_int 为 4 字节(根据机器字长而定)
#define _ADNBND (sizeof (acpi_native_int) - 1)
/*
* Variable argument list macro definitions
*/
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
分析以下三个宏的作用
1) va_start 宏
作用: 根据 A 取得可变参数表的首指针并赋值给 ap。
原理: 根据最后一个固定参数 A 的地址 + 第一个变参对 A 的偏移地址,然后赋值给 ap,这样 ap 就是可变参数表的首地址(函数传递的参数会从右向左依次入栈,并且 ARM 的栈为降栈,所以参数 A 的地址最低)。
2) va_arg 宏
作用: 指取出当前 ap 所指的可变参数并将 ap 指针指向下一可变参数。
3) va_end 宏
作用: 结束可变参数的获取,与 va_start 对应使用。
堆栈中,各个函数的分布情况是倒序的,即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分。参数在堆栈中的分布情况如下:
**************************
最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数的局部变量
**************************
得到可变参数个数的三种办法:
1) 函数的第一个参数,指定后续的参数个数,如 func(int num,...);
2) 根据隐含参数,判断参数个数,如 printf 系列的,通过字符串中 % 的个数判断;
3) 特殊情况下(如参数都是不大于 0xFFFF 的 int),可以一直向低处访问堆栈,直到返回地址。
而 _bnd(X, bnd) 宏就是以 4 字节对齐的变量 X 的大小。
自己实现一个简单的 printf 函数
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
void print(char *fmt, ...)
{
va_list ptr;
va_list temp_ptr = NULL;
/* 用于存储最终转换的结果 */
char buf[100] = {0};
/* 用于存储临时转换的结果 */
char temp_buf[50] = {0};
unsigned char index = 0;
unsigned char len, arg_len;
len = strlen(fmt);
/* 得到可变参数的首地址 */
va_start(ptr, fmt);
for(; *fmt; fmt++){
if(*fmt == '%'){
switch(*++fmt){
case 'd':
case 'D':
sprintf(temp_buf, "%d", va_arg(ptr, int));
temp_ptr = temp_buf;
break;
case 's':
case 'S':
/* 取出当前变量,并将指针指向下一个可变参数 */
temp_ptr = va_arg(ptr, char*);
break;
}
arg_len = strlen(temp_ptr);
strcat(buf+index, temp_ptr);
index += arg_len;
}else{
buf[index] = *fmt;
index++;
}
}
/* 结束取参 */
va_end(ptr);
/* 输出最终转换结果 */
puts(buf);
}
int main()
{
print("My name is %s and my height is %d cm.", "Lance#", 178);
return 0;
}
程序运行结果:
My name is Lance# and my height is 178 cm.
C 语言精髓之变参函数的更多相关文章
- 试读《JavaScript语言精髓与编程实践》
有幸看到iteye的活动,有幸读到<JavaScript语言精髓与编程实践_第2版>的试读版本,希望更有幸能完整的读到此书. 说来读这本书的冲动,来得很诡异,写一篇读后感,赢一本书,其实奖 ...
- 《JavaScript语言精髓与编程实践》读书笔记
JavaScript语言精髓与编程实践读书笔记 function v1(v1){ v1 = 100; alert('v1:'+v1); } function v2(name){ v1.apply(th ...
- JavaScript语言精髓(1)之语法概要拾遗(转)
JavaScript语言精髓(1)之语法概要拾遗 逻辑运算 JavaScript中支持两种逻辑运算,“逻辑或(||)”和“逻辑与(&&)”,他们的使用方法与基本的布尔运算一致: v ...
- js:语言精髓笔记12--动态语言特性(2)
对于括号内: 通过赋值时发生的重写: (Object1 = function() {}).prototype.value = 100; var obj1 = new Object1; console. ...
- js:语言精髓笔记11--动态语言特性(1)
语言:程序最终被表达为数据(结构)和逻辑(算法),命令式和说明式/函数式语言分别从这两方面分类: 动态:在语言陈述时无法确定,必须在计算机执行时才能确定语言关系:JS是完全动态语言,导致其不确定性一般 ...
- js:语言精髓笔记9--函数式语言特征
形式化运算系统的研究: 图灵:提出图灵机形式系统,通过0,1运算系统来解决复杂问题: 冯诺依曼:提出了冯诺依曼体系:即通过修改内存反映运算结果: 阿隆左.丘奇:提出新的运算范型Lambda演算,计算机 ...
- js:语言精髓笔记8--对象系统
封装: 一把对象系统,封装是由语法解析来实现的,即语法作用域:但js是动态语言,因此只能依赖变量作用域: js的变量作用域只有表达式,函数,全局三种:所以js只能实现public和private两种封 ...
- js:语言精髓笔记7----原型继承
面向对象有三个基本特性:封装,继承,多态:如果都满足的话称为面向对象语言:而部分满足则称为基于对象语言: 数据类型实现模型描述: JavaScript对象模型: 构造过程:函数->构造器 构造器 ...
- js:语言精髓笔记5----语言分类
计算模型:源于对计算过程的不同认识: 1.基于不同计算模型一般分为://教科书的一般分类 命令式语言: 函数式语言: 逻辑式语言: 面向对象程序设计语言: 2.基于程序本质分类: //编程的经典法则 ...
随机推荐
- 部署eclipse项目到tomcat
1.为了以防万一,将本地tomcat版本及其jdk版本与服务器上的版本最好是相同的 2.在本地eclipse下运行项目即可发布(注意(1)数据库连接的是服务器数据库还是本地数据库(2)运行项目前先cl ...
- html 2
一.列表 信息资源的一种展示形式 二.列表的分类 1.有序列表 <ol> <li>列表项1</li> <li>列表项2</li> </ ...
- 04-jQuery的属性操作
jquery的属性操作模块分为四个部分:html属性操作,dom属性操作,类样式操作和值操作 html属性操作:是对html文档中的属性进行读取,设置和移除操作.比如attr().removeAttr ...
- 响应式网站设计(Responsive Web design)
页面的设计与开发应当根据用户行为以及设备环境(系统平台.屏幕尺寸.屏幕定向等)进行相应的响应和调整.具体的实践方式由多方面组成,包括弹性网格和布局.图片.CSS media query的使用等.无论用 ...
- 使用tomcat插件运行java web项目
1 新建javaweb项目 使用骨架创建javaweb项目,具体步骤不熟悉的参见上一篇文章[idea集成maven]. 2 添加依赖 <dependency> <groupId> ...
- [Mac]macOS Mojave :发现 Mac 的新功能。
1.深色模式 换种颜色看 Mac “深色模式”为桌面和内建应用带来更生动的外观,可让您轻松专注于最重要的内容. 若要在浅色和深色外观之间切换,请打开“系统偏好设置”并点按“通用”. 2.叠放 整理桌面 ...
- IDEA 图标介绍。 缓存和索引介绍、清理方法和Debug使用
一.图标 二.缓存和索引 IntelliJ IDEA 的缓存和索引主要是用来加快文件查询,从而加快各种查找.代码提示等操作的速(上图中的图标能这样显示也是靠索引).某些特殊条件下,IntelliJ I ...
- Testing - 软件测试知识梳理 - 测试方法
选择和使用测试方法和工具 按照测试需求用途(或测试技巧)选择 在软件开发生命周期和软件测试流程中适当地选择 按照测试人员实际技能选择 选择可提供的和可执行的 测试方法 类别及技巧 目标 使用方法 举例 ...
- jvm-垃圾回收gc简介+jvm内存模型简介
gc是jvm自动执行的,自动清除jvm内存垃圾,无须人为干涉,虽然方便了程序员的开发,但同时增加了开发人员对内存的不可控性. 1.jvm内存模型简介 jvm是在计算机系统上又虚拟出来的一个伪计算机系统 ...
- Servlet-生命周期简介
Servlet生命周期可分为5个步骤 加载Servlet.当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例 初始化.当Servlet被实例化后,Tomcat会调 ...