一、可变参数函数原型

可变参数函数的原型声明格式为:type VAFunction(type arg1, type arg2, … );

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"…"表示。固定参数和可选参数共同构成一个函数的参数列表。

其实,对于c语言的不定参数问题,只需要三个函数就可以搞定了。这三个函数的头文件是,其实下面的三个函数都是一个宏定义。

void va_start(va_list ap, last);

type va_arg(va_list ap, type);

void va_end(va_list ap);

在这三个函数解释之前,先看一个变量va_list,这个变量的类型是什么呢?通过查看源代码,发现它的类型是char*类型的。

对于va_start(va_list ap, last)函数,这个函数是用来初始化指针变量ap的,使参数列表指针ap指向函数参数列表中的第一个可选参数,说明:last是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void

va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数last为c,因此就是va_start(ap,

c)。

对于va_arg(va_list ap, type)函数来说,就是先返回ap指针所指的那个参数值,再将ap指针按照type类型向后移动,指向参数列表的下一个参数。

对于va_end(va_list ap):清空参数列表,并置参数指针ap无效。一般和va_start(va_list ap, last)配套使用。

这里有一个问题,当我们取参数的时候,如何判断我们要取的参数已经取完了?

二、可变参数在编译器中的处理

我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98 \include\stdarg.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)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。

3、va_start的定义为&v+_INTSIZEOF(v),而&v是最后一个固定参数的起始地址,再加上其大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。

在这里  要注意2点:

⑴在intel+windows的机器上,函数栈的方向是向下的,栈底指针的内存地址高于栈顶指针的内存地址,所以先进栈的数据是存放在内存的高地址处。

(2)在VC等绝大多数C编译器中,参数进栈的顺序是由右向左的,因此,

参数进栈以后的内存模型如下图所示:最后一个固定参数的地址正好位于第一个可变参数之下,并且是连续存储的。

|—— —————————————|

|  最后一个可变参数                       |   ->高内存地址处

|— ——————————————|

........................

|------------------------------------------|

| 第N个可变参数                            |

|------------------------------------------|

...................

|——— ————————————|

|  第一个可变参数                           |

|——————————————— |

|————————————— ——|

|  最后一个固定参数                       |

|—————————————— —|

...............

|——————————————-  |

|——————————————— |  -> 低内存地址处

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》的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。

四、小结:

1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。

2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如

⑴在固定参数中设标志—— printf函数就是用这个办法。

⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。下面的例子就是采用这个办法——当可变参数的值为-1时,即认为得到参数列表的结尾。

无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。这是一个不太方便

3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:

①函数栈的生长方向

②参数的入栈顺序

③CPU的对齐方式

④内存地址的表达方式

结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的 实现,最后va_end的存在则是良好编程风格的体现—将不再使用的指针设为NULL,这样可以防止以后的误操作。

4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信有经验的读者就可以写出适合于自己机器的实现来。

五、实例

#include

#include

#include

int sum(const int ,...);

int main(void)

{

printf("The reslut id:%d\n",sum(10,9,8,-1));

return 0;

}

int sum(const int first,...)

{

va_list argp;

int sum=0;

int tmp;

va_start(argp,first);

sum+=first;

printf("%d\n",first);

while((tmp=va_arg(argp,int))!=-1)

{

printf("%d\n",tmp);

sum+=tmp;

}

va_end(argp);

return sum;

}

c语言中代码参数太少,C语言不定参数探究相关推荐

  1. c语言函数调用参数太少,调用input_message的参数太少在主函数中 是神马意思 大神们帮帮我...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 /*日期结构体类型*/ struct date { int year; int month; int date; }; /*图书证结构体类型*/ #inc ...

  2. 函数调用的参数太少c语言,调用input_message的参数太少在主函数中 是神马意思 大神们帮帮我...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 /*日期结构体类型*/ struct date { int year; int month; int date; }; /*图书证结构体类型*/ #inc ...

  3. C++中出现错误“函数的参数太少”

    出现错误"函数的参数太少" 原因:实际参数和形式参数的数量不同 int f(int a,int b) f(m)   这样是错误的,形式参数有2个,实际参数应该有2个,而这只有1个. ...

  4. 用于调用的参数太少/写入位置时发生冲突/检测到无效的异常处理程序例程

    用于调用的参数太少/写入位置时发生冲突/检测到无效的异常处理程序例程 针对新人学C过程中可能遇到的一类问题 我们在学习中经常遇到这种情况: #include<stdio.h> void m ...

  5. python连接access 参数太少_带参数的PypyODBC:[ODBC Microsoft Access Driver]参数太少。预期4...

    我使用pypyodbc从access数据库中选择数据.我使用下面的查询,其中有三个指定的参数.在 我试过几种品种,但没有效果.我看不出我的语法有什么问题.在SELECT [Date], [Time], ...

  6. 反转?MIT女博士遭质疑:代码贡献太少,黑洞照片功臣另有其人

    问耕 郭一璞 发自 凹非寺 量子位 出品 | 公众号 QbitAI △ 图片来自@biancanazari 人类首张黑洞照片火了,"洗"出照片的MIT女博士也火了. 她为黑洞最终现 ...

  7. python语言的注释语句引导符不包括什么_以下选项中,哪一个是Python语言中代码注释使用的符号?________...

    [单选题]关于 Python 语句 P = –P,以下选项中描述正确的是________ [多选题]Python的数字类型包括( ) [多选题]Python中的注释符有哪几种?( ) [判断题]已知 ...

  8. python语言中代码注释可以使用_以下选项中,Python语言中代码注释使用的符号是: ( )...

    以下选项中,Python语言中代码注释使用的符号是: ( ) 答:# 中国古代舞蹈灿烂辉煌,但在理论研究方面却相对薄弱,这种情况直到明清都无显著改变. 答:错误 Photoshop中下列工具中不可以定 ...

  9. c语言中申请内存并初始化,c语言中结构体的定义、初始化及内存分配

    #include struct person { char *name; int age; }; int main() { //结构体可以定义在函数内,也可以定义到函数外 //相当于全局变量与局部变量 ...

最新文章

  1. 用python编写函数回归分析_用python编写函数的logistic回归系数
  2. java验证码实现简单_java实现简单的验证码功能
  3. 重谈ExtGrid 扩展行自动展开(一)(expanded row 默认展开)
  4. [WCF权限控制]利用WCF自定义授权模式提供当前Principal[实例篇]
  5. 文献学习(part29)
  6. 算法:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。...
  7. 485转232转换器产品介绍及性能参数介绍
  8. 解决问题:HTTP 错误 401.1 - 未授权:登录失败【转】
  9. 启动服务_用控制台启动一个wcf服务
  10. Jenkins教程(八)实现 GitLab 触发 Jenkins 自动按模块发布前端
  11. UCF101视频分类之CNN-LSTM-Code总结
  12. LaTex常用特殊符号对应表
  13. MPEG4与.mp4
  14. PyCharm配置SSH和SFTP连接远程服务器
  15. PCIE Feature ------ INTx
  16. 订阅切换按钮(subscribe toggle button)
  17. 减少在线去重造成的数据碎片
  18. 汇编语言-CPU如何区分指令和数据
  19. 【Verilog基础】CMOS逻辑门实现基础功能(反相器/与非门/或非门/与门/或门)
  20. K均值聚类以及matlab实现

热门文章

  1. C# .NET 使用 NPOI 生成 .xlsx 格式 Excel
  2. idea创建javaweb项目连接mysql【HTTP Status 500】
  3. 【服务端渲染】之 Vue SSR
  4. C#LeetCode刷题之#443-压缩字符串​​​​​​​(String Compression)
  5. C#LeetCode刷题之#453-最小移动次数使数组元素相等(Minimum Moves to Equal Array Elements)
  6. vm ubuntu设置中文_如何在本地Ubuntu Linux机器或VM上设置LAMP服务器
  7. ubuntu16.04安装docker(阿里云镜像)
  8. ubuntu命令之dpkg
  9. 【寻子】人脸识别与寻子的碰撞
  10. npm安装vue-cli时报错解决方法