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循环一一取出函数中的参数。在此使用第一个函数参数表示传递可变参数的个数。在使用变参函数的时候,必须知道参数什么时候结束。如果没有给出变参函数的个数,直接给出第一个参数,那么必须约定一个参数作为结束标志。

c语言函数的形参和实参(轻松从底参理解变参函数的实现)(1)

在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

    分享
    投诉
    首页