免费预览已结束,剩余1页可下载查看
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
前言:本文在很大程度上改编自网友kevintz的“C语言中可变参数的用法”一文,在行文之前先向这位前辈表示真诚的敬意和感谢。 一、什么是可变参数 我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, .); 它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“”做参数占位符),实际调用时可以有以下的形式: printf( %d ,i); printf( %s ,s); printf( the number is %d ,string is:%s , i, s); 以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文就这个问题进行一些探讨,希望能对大家有些帮助. 二、写一个简单的可变参数的C函数 先看例子程序。该函数至少有一个整数参数,其后占位符,表示后面参数的个数不定. 在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值. 函数代码如下: /示例代码1:可变参数函数的使用 #include stdio.h #include stdarg.h void simple_va_fun(int start, .) va_list arg_ptr; int nArgValue =start; int nArgCout=0; /可变参数的数目 va_start(arg_ptr,start); /以固定参数的地址为起点确定变参的内存起始地址。 do +nArgCout; printf( the %d th arg: %dn ,nArgCout,nArgValue); /输出各参数的值 nArgValue = va_arg(arg_ptr,int); /得到下一个可变参数的值 while(nArgValue != -1); return; int main(int argc, char* argv) simple_va_fun(100,-1); simple_va_fun(100,200,-1); return 0; 下面解释一下这些代码 从这个函数的实现可以看到,我们使用可变参数应该有以下步骤: 由于在程序中将用到以下这些宏: void va_start( va_list arg_ptr, prev_param ); type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件. 函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变 量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。 然后用va_start宏初始化中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数. 然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。 设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,读者在看完这几个宏的内部实现机制后,自然就会明白。(二)可变参数在编译器中的处理 我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC+6.0中stdarg.h里的代码(文件的路径为VC安装目录下的vc98includestdarg.h) typedef char * va_list; #define _INTSIZEOF(n) (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) ) #define va_end(ap) ( ap = (va_list)0 ) 下面我们解释这些代码的含义: 1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的 2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。 3、va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。这里要知道两个事情: 在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。 (2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。 | | 最后一个可变参数 | - 高内存地址处 | . | | 第N个可变参数 | - va_arg(arg_ptr,int)后arg_ptr所指的地方, | | 即第N个可变参数的地址。 | | . | | 第一个可变参数 | - va_start(arg_ptr,start)后arg_ptr所指的地方 | | 即第一个可变参数的地址 | | | | | | | 最后一个固定参数 | - start的起始地址 | | . | | | | | | - 低内存地址处 (4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。 因此,现在再来看va_arg()的实现就应该心中有数了: #define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) ) 这个宏做了两个事情, 用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值 计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。 (5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为(void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的. (三)可变参数在编程中要注意的问题 因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 例如,在C的经典教材the c programming language的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。 (四)小结: 1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。 2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如 在固定参数中设标志 printf函数就是用这个办法。后面也有例子。 在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法. 无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。 3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定: 函数栈的生长方向 参数的入栈顺序 CPU的对齐方式 内存地址的表达方式 结合源代码,我们可以看出va_list的实现是由决定的,_INTSIZEOF(n)的引入则是由决定的,他和又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。 4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。下面就是一个例子用模板可以实现 template T sum(T a, T2 b, .) 可以实现的,吧上边的代码一改 template T t_max(T start,T2 next.) T result = 0; va_list arg_ptr; T nArgValue =start; va_start(arg_ptr,start); /以固定参数的地址为起点确定变参的内存起始地址。 do result += nArgValue; cout typeid(nArgValue).name() endl;/显示类型 nArgValue = va_arg(arg_ptr,int); /得到下一个可变参数的值 while(nArgValu
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 擦玻璃机构课程设计
- 编程基础课程设计
- 笔记本修理课程设计
- 程序设计课程设计测试员
- 触发计数器课程设计
- 包装运输课程设计
- 基于PID的直流电机调速系统优化实践课程设计
- 学习行为数据采集课程设计课程设计
- 表面技术课程设计
- WebGL粒子系统优化方法课程设计
- 寄生虫病防治技能竞赛试题及答案
- 素养与情操-美术鉴赏的意义
- SH/T 3075-2024 石油化工钢制压力容器材料选用规范(正式版)
- (高清版)JTGT 5640-2020 农村公路养护预算编制办法
- 武汉市2024届高三年级五月模拟训练(五调)英语试卷(含答案)+听力材料
- 行政伦理学教程(第四版)课件 第1章 行政伦理的基本观念
- 矿井通风与安全-金属非金属矿山
- 软件著作权合作协议
- 场景速写 风景速写 建筑速写
- 吉利NPDS流程和PPAP介绍
- 二年级综合实践教案
评论
0/150
提交评论