c语言函数的形参和实参(轻松从底参理解变参函数的实现)
我们知道,数组作为函数参数时退化为一个指针,再加上一个数组长度的参数,便可以处理数组:
void(int n, int arr[]);
其实这就是告诉编译器,以arr为基准地址,处理n个int类型的数据。
我们也知道,对于C函数,函数参数的压栈操作是从右向左进行的(字长内存对齐),从最后一个参数开始压栈,直到将第一个参数压栈为止。
由此,如果有一个基准地址及数据长度,似乎可以对不确定函数参数个数的函数进行处理了。
在C中,正是以符号“…”作为占位符来进行变参(函数参数数量可变)处理的。
#include <stdio.h>
void print(int n, ...)
{
int *p,i;
p = &n 1;
for (i=0;i<n;i )
printf("%d\t",p[i]);
printf("\n");
return ;
}
int main()
{
print(4,12,34,56,78);
return 0;
}
从上面的代码可以看到,首先在print()函数中使用了占位符“…”,因此该函数在编译的时候被当成变参函数来处理,对该函数调用中的参数一一 进行压栈处理。如下图所示为函数参数在栈中的存储结构。上述代码定义了一个int型指针变最P,由于函数参数的压栈顺序是从右向左,由髙地址到低地址,所以在函数中通过“p=&n 1;”得到的是第一个可变参数的地址,接下来通过一个for循环一一取出函数中的参数。在此使用第一个函数参数表示传递可变参数的个数。在使用变参函数的时候,必须知道参数什么时候结束。如果没有给出变参函数的个数,直接给出第一个参数,那么必须约定一个参数作为结束标志。
在stdarg.h中,通过宏来实现变参函数的泛型化:
#include <stdio.h>
#include <stdarg.h>
void print(int n,...)
{
int arg,i;
va_list p;
va_start(p,n);
for(i=0;i<n;i )
{
arg = va_arg(p,int);
printf("%d\t",arg);
}
printf("\n");
va_end(p);
return ;
}
int main()
{
print(3,21,32,54);
return 0;
}
宏定义如下:
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 )
宏va_start(ap,v)可以取得第二个参数的地址,存储到char *类型的ap。
宏_INTSIZEOF(v)用于于内存对齐(编译器都有默认的内存对齐,通常是一个字长)。
宏va_arg(ap,t)用于实现ap的的类型转换,并通过 =实现ap的副作用,指向下一下参数的地址。
宏va_end(ap)用于将指针ap置0。
~(sizeof(int) - 1)的值对于32位系统是-4,二进制为:
1 1111111 11111111 11111111 11111100
宏_INTSIZEOF(v)的表达式能够实现类型v按4个字节(32位系统)进行类型对齐(编译器会默认按一个字长来对齐内存,int一般也是定义为一个字长的长度)。
以下实例是实现n个int类型做函数参数的实例:
#include <stdio.h>
typedef char * va_list;
#define va_start(ap,v) ( ap = (va_list)&v sizeof(v) )
#define va_arg(ap,t) ( *(t *)((ap = sizeof(t)) - sizeof(t)) )
#define va_end(ap) ( ap = (va_list)0 )
void print(int n,...)
{
int arg,i;
va_list p;
va_start(p,n);
for(i=0;i<n;i )
{
arg = va_arg(p,int);
printf("%d\t",arg);
}
printf("\n");
va_end(p);
return ;
}
int main()
{
print(4,12,34,56,78);
return 0;
}
因为int类型的长度与编译器默认内存对齐的长度一致,所以不需要内存对齐的宏_INTSIZEOF(v)。
如果实现n个字符类型的函数参数,就需要添加内存对齐的宏了:
#include <stdio.h>
typedef char * va_list;
#define va_start(ap,v) ( ap = (va_list)&v sizeof(v) )
#define va_arg(ap,t) ( *(t *)((ap = sizeof(t)) - sizeof(t)) )
#define va_end(ap) ( ap = (va_list)0 )
void print(int n,...)
{
int arg,i;
va_list p;
va_start(p,n);
for(i=0;i<n;i )
{
arg = va_arg(p,char);
printf("%c\t",arg);
}
printf("\n");
va_end(p);
return ;
}
int main()
{
print(4,'A','B','C','D');
return 0;
}
如果是不同类型的n个函数参数呢?则需要逐一分析类型来实现数据解析和地址的偏转了:
#include <stdio.h>
#include <stdlib.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 )
void myprintf(char* fmt, ...)
{
va_list p;
char c;
va_start(p,fmt);
do
{
c =*fmt;
if (c != '%')
{
putchar(c);
}
else
{
switch(* fmt)
{
case 'd':
printf("%d",*((int*)p));
break;
case 'c':
printf("%c",*((int*)p));
break;
case 'f':
printf("%3.2f",*((double*)p));
va_arg(p,int);
default:
break;
}
va_arg(p,int);
}
fmt;
}while (*fmt != '\0');
va_end(p);
return;
}
void main()
{
int a = 12;
short b = 56;
char c = 'A';
double f = 123.2;
myprintf("a = %d\t b = %d\t c = %c\t f = %f\n",a,b,c,f);
return ;
}
-End-
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com