19、关于va_list,va_start,va_arg,va_end
前面,我总结过关于va_list,va_start,va_arg,va_end的一些知识点:
http://www.cnblogs.com/mydomain/archive/2010/07/27/1785667.html
http://www.cnblogs.com/mydomain/archive/2010/12/06/1898140.html
今天看到一篇文件,写的也言简意赅,喜欢就转载一下,原谅地址是:
http://www.cnblogs.com/wubiyu/archive/2008/07/30/1256860.html
不过,本文中,这一句“(2)va_arg()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。”没有介绍清楚,具体参见
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
第一个地址所链接文章。
va_list的用法:
(1)首先在函数里定义一具va_list型的变量,这个变量是指向参数的指针
(2)然后用va_start宏初始化变量刚定义的va_list变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
(3)然后用va_arg返回可变的参数,va_arg的第二个参数是你要返回的参数的类型。
(4)最后用va_end宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用va_arg获取各个参数。
va_list在编译器中的处理:
(1)在运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址。
(2)va_arg()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。
(3)va_end(),x86平台定义为ap = ((char*)0),使ap不再指向堆栈,而是跟null一样,有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如 gcc在linux的x86平台就是这样定义的。
要注意的是:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。
使用va_list应该注意的问题:
(1)因为va_start, va_arg, va_end等定义成宏,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型,也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现。
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
小结:可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变函数的c函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数,如果在c++里,我们应该利用c++多态性来实现可变参数的功能,尽量避免用c语言的方式来实现。