现在的位置: 首页 > 综合 > 正文

C语言可变参数函数执行原理以应用

2012年02月22日 ⁄ 综合 ⁄ 共 3878字 ⁄ 字号 评论关闭

参数可变函数又称VA函数,例如printf,scanf,exec。

1.举例:

//fun:打印n后面参数的值

void fun(int n, ...);

int main()

{

     int part1 = 128;

     int part2 = 256;

     int part3 = 512;

     fun(part1, part2, part3);

     return 0;

}

void fun(int part1, ...)

{

     int *p = &part1; //获取part1的地址

     printf("%d\n", *++p);//打印part2的值

     printf("%d\n", *++p);//打印part3的值

}

C默认的函数调用规范是_cdecl,即所有参数从右到左依次入栈,严格的fun声明应该是:

void _cdecl fun(int n, ...);

在main中,调用fun函数前先将参数入栈,入栈的顺序是:

push part3

push part2

push part1

然后调用fun,执行函数体代码。

先压栈的参数会放在高地址,因为栈是由高地址往低地址生长的,所以part1,part2,part3在内存中的顺序将会是:

0xFE6C part3

0xFE70 part2

0xFE74 part1

这样可以通过取得第一个参数的地址&part1和++操作分别访问到后面的参数,但这必须是_cdecl函数调用规范,例如printf系列的库函数(sprintf,fprintf)都是可以接受可变参数的函数。假设有下面一条语句:

printf("%d %d %d\n", m, n, k);

可以看出,我们可以通过第一个参数得到参数的个数(格式符的数目)和类型(例如%d),这就是为什么printf("%d %d\n", m, n, k)可以成功执行,而printf("%d %d %d\n", m, n)会失败的原因了。传递的参数如果多于格式符的个数可以忽略掉(在Linux下能正常执行),但是少于就会出现访问越界(Linux下会报警告,但是仍然正常执行,越界的参数会是个随机值)。(执行自己程序的时候记得加./,例如./a.out)

2.采用varargs宏来编写支持可变参数列表的函数,在ASCI C标准里,这些宏包含在stdarg.h。

例如以下代码:

#include <stdio.h>

#include <stdarg.h>

void _cdecl fun(int n, ...);//可以不加_cdecl

int main(int argc, char *argv[])

{

     int part1 = 128;

     int part2 = 256;

     int part3 = 512;

     fun(part1, part2, part3);

     return 0;

}

void fun(int n, ...)

{

     va_list ap;

     va_start(ap, n);

     printf("%d\n", va_arg(ap, int));

     printf("%d\n", va_arg(ap, int));

     va_end(ap);

}

在Microsoft为VC提供的实现中,可以看到这样的定义:

#define _ADDRESSOF(v) (&(v))

#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

typedef char* va_list;

#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )//ap指向v下一个参数的地址

#define va_arg(ap,t) (*(t*)( (ap += _INTSIZEOF(t)) -  _INTSIZEOF(t)) )   //ap指向下一个参数的地址,但是返回的还    //是这个参数的值

#define va_end(ap) ( ap = (va_list)0 )

va_list 一个char型指针,每次单个字节寻址。

va_start 通过_INTSIZEOF计算参数类型大小,并让ap获得v后面参数对象的地址

va_arg ap指向参数列表中ap下一个参数对象,并返回ap之前指向的t类型参数对象

其中_INTSIZEOF(n)就是将n的长度化为int长度的整数倍。如果n是char类型,那么sizeof(n)=1,化为int长度的整数倍就应该是4,假设sizeof(n)=4m+k(m>=0,k=0,1,2,3),sizeof(n)+4-1=4m+(k+3),~(sizeof(int)-1)=~(4-1)=~(00000011b)=11111100b,这样任何数&~(sizeof(int)-1)后,后两位肯定是0,那就是4的倍数了,(4m+(k+3))&~(sizeof(int)-1)可以保证能存放4m+k。

将这些宏还原重写fun函数:

void fun(int part1, ...)

{

     int part2, part3;

     char *ap;//va_list ap;

     ap = (char *)&part1+4;//va_start(ap, part1), ap指向part2

     part2 = *(int *)(ap+=4 - 4);//返回part2,ap指向part3,part2=va_arg(ap,int)

     part3 = *(int *)(ap+=4 - 4);//返回part2,ap指向part3,part3=va_arg(ap,int)

     ap=(char *)0;//ap指向空,var_end(ap)

}

若part2为char,shor则会自动转化成int型,float则会自动转化成double型。

#include <stdio.h>
#include <stdarg.h>
void fun(int n, ...);

int main(int argc, char *argv[])
{
     int part1 = 128;
     float part2 = 256.0;//float
     float part3 = 512.0;
     fun(part1, part2, part3);
     return 0;
}
void fun(int n, ...)
{
     va_list ap;
     va_start(ap, n);
     printf("%f\n", va_arg(ap, double));//double正确,float错误,系统自动存储为double型
     printf("%f\n", va_arg(ap, double));
     va_end(ap);
}

#include <stdio.h>
#include <stdarg.h>
void fun(int n, ...);

int main(int argc, char *argv[])
{
     int part1 = 128;
     char part2 = 'c';//char
     short part3 = 8;//short
     fun(part1, part2, part3);
     return 0;
}
void fun(int n, ...)
{
     va_list ap;
     va_start(ap, n);
     printf("%c\n", va_arg(ap, int));//char自动存储为int,不能用char,即使这样,仍然可用%c输出字符
     printf("%d\n", va_arg(ap, int));//short自动存储为int,不能用short
     va_end(ap);
}

以上这些问题都是内存对齐造成的。

3.vprintf,vfprintf,vsprintf,vsnprintf,vasprintf格式化输出,有一个va_list参数

#include <stdio.h>

#include <stdarg.h>

int vprintf(const char *format, va_list ap);//格式化输出到标准输出,对应到printf

int vfprintf(FILE *stream, const char *format, va_list ap);//格式化输出到文件流,对应到fprintf

int vsprintf(char *s, const char *format, va_list ap);//格式化输出到字符串,对应到sprintf

int vsnprintf(char *s, size_t n, const char *format, va_list ap);//格式化输出固定长度到字符串,对应到snprintf

int vasprintf(char **ret, const char *format, va_list ap);//对应到asprintf

举例,用vprintf实现error:

#include <stdio.h>

#include <stdarg.h>

void error(char *function_name, char *format, ...)

{

     va_list ap;

     va_start(ap, format);

     /*print out name of function causing error*/

     (void)fprintf(stderr, "ERR in %s:", function_name);

     /*print out remainder of message*/

     (void)vfprintf(stderr, format, ap);

     va_end(ap);

     (void)abort();

}

抱歉!评论已关闭.