一.何谓可变参数
int printf( const char* format, ...);
这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用”…”表示).
而我们又可以用各种方式来调用printf,如:
printf( "%d ",value); printf( "%s ",str); printf( "the number is %d ,string is:%s ", value, str);
二.实现原理
C语言用宏来处理这些可变参数。
这些宏看起来很复杂,其实原理挺简单:就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参 数的地址.下面我们来分析这些宏.
在VC中的stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:
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 )
可以用下图来表示:
在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
|最后一个可变参数 | -> 高内存地址处
|——————————————————————————|
...................
|——————————————————————————|
|第N个可变参数 | ->
va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|——————————————— |
………………………….
|——————————————————————————|
|第一个可变参数 | ->
va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|——————————————— |
|———————————————————————— ——|
| |
|最后一个固定参数 | -> start的起始地址
|—————————————— —| .................
|—————————————————————————— |
| |
|——————————————— |-> 低内存地址处
三.printf研究
下面是一个简单的printf函数的实现,参考了中的156页的例子,读者可以结合书上的代码与本文参照。
#include <stdio.h> #include <stdlib.h> //一个简单的类似于printf的实现,//参数必须都是int 类型 void myprintf(char* fmt, ...){ char* pArg=NULL; //等价于原来的va_list char c; pArg = (char*)&fmt; //注意不要写成p = fmt !!因为这里要对参数取址,而不是取值 pArg += sizeof(fmt); //等价于原来的va_start do{ c =*fmt; if (c != ‘%‘){ putchar(c); //照原样输出字符 }else{ //按格式字符输出数据 switch(*(++fmt)){ case ‘d‘: printf( "%d",*((int*)pArg)); break; case ‘x‘: printf( "%#x",*((int*)pArg)); break; default: break; } pArg += sizeof(int); //等价于原来的va_arg } ++fmt; }while (*fmt != ‘‘); pArg = NULL; //等价于va_end return; } int main(){ int i = 1234; int j = 5678; myprintf( "the first test:i=%d ",i); myprintf( "the secend test:i=%d; %x;j=%d; ",i,0xabcd,j); system( "pause "); return 0; }
输出:
the first test:i=1234 the secend test:i=1234; 0xabcd;j=5678;
四.应用
求最大值:
#include <stdarg.h>//不定数目参数需要的宏 #include <stdio.h> int max(int n,int num, ...){ int m = num; int i = 0; va_list x;//说明变量x va_start(x,num);//x被初始化为指向num后的第一个参数 for(i=1; i<n; i++){ //将变量x所指向的int类型的值赋给y,同时使x指向下一个参数 int y = va_arg(x,int); if(y>m){ m=y; } } va_end(x);//清除变量x return m; } int main(){ int ret1 = max(3,5,56,55); int ret2 = max(6,0,4,32,45,533,6565); printf( "%d,%d ",ret1,ret2); return 0; }
输出:
56,6565