在一些函数中参数个数可变
例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。
函数的所有参数是存储在线性连续的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。
使用可变参数应该有以下步骤:
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.
1. 首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
2. 然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
3. 然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型
4. 最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数。 va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在Linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
下面用自己写的一段简单的模拟实现printf函数功能的代码来了解可变参数:
#include<stdio.h>
#include<stdarg.h>
void print_0(char* str)//字符串的打印
{
while(*str!='\0')
{
putchar(*str);
str++;
}
}
void print_1(int i)//整型的打印
{
if(i>9)
print_1(i/10);//递归处理
putchar(i%10+'0');
}
void my_print(char* format, ...)
{
va_list arg;//定义一个va_list型的变量,这里和charr*是一样的
va_start(arg, format);
//以固定参数的地址为起点确定变参的内存起始地址,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数
while(*format!='\0')
{
switch(*format)
{
case 's':
print_0(va_arg(arg, char*));//遇到's'时就访问下一个参数
break;
case 'c':
putchar(va_arg(arg,char));//访问下一位参数,是宏可以以类型作为参数
break;
case 'd':
{
int ret = 0;
ret = va_arg(arg, int);//访问下一位参数
print_1(ret);
}
break;
default:
putchar(*format);
break;
}
format++;
}
va_end(arg);//设定结束标志,arg参数宏定义结束,相当于将指针置空
}
int main()
{
my_print("s ccccc! d%.","hello",'w','o','r','l','d',100);
//这里的参数有8个,第一个参数为固定参数
return 0;
}
每次调用va_arg意味着上次调用时的参数指针加一,也就是调用的是相对于上次调用的参数的下一位。
下面我们从内存中了解下可变参数的用法,图解如下:
(1)在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处
(2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
(3)当宏传参为类型时,开辟相应类型的空间以对其操作,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么 printf 中不是实现了智能识别参数吗?那是因为函数 printf 是从固定参数 format 字符串来分析出参数的类型,再调用 va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的,正如例子代码中的 switch…case 语句所做一般。
当然,这里讲到的存储方式以及代码都是在VS2008下进行的,若有不当之处,恳请指正