当前位置:首页 » 编程语言 » c语言的可变参数函数

c语言的可变参数函数

发布时间: 2024-03-02 23:43:58

c语言奇淫技巧之函数的可变参数

前言:

有时候我们需要一个函数接口可以支持可变参数以提高其泛化能力,比如说定义一个求和函数,对传入的所有参数进行求和并返回,C语言提供了一种可变参数的方式来帮助我们实现。

原理:

函数参数是从右向左线性连续依次入栈,如果我们可以知道可变参数列表的前一个参数的地址和类型,就可以得知可变参数列表的首地址,进而根据每个参数的类型取出相应的数据。简单来说就是将栈里面的数据,按照指定类型的大小,依次取出。

具体实现:

步骤解析:



㈡ c语言中的void printlog(char *format,...)这是什么意思

是可变参数,是c的一个语法现象,我在电脑上保存的一些资料,希望对你有用。
一、什么是可变参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点"…"做参数占位符),实际调用时可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文就这个问题进行一些探讨,希望能对大家有些帮助.

二、可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98\include\stdarg.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 )

下面我们解释这些代码的含义:

1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的

2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。

3、va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。

这里要知道两个事情:
⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
(2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|--------------------------|
| 最后一个可变参数 | ->高内存地址处
|--------------------------|
|--------------------------|
| 第N个可变参数 | ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|--------------- |
|--------------------------|
| 第一个可变参数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|--------------- |
|------------------------ --|
| |
| 最后一个固定参数 | -> start的起始地址
|-------------- -| .................
|-------------------------- |
| |
|--------------- | -> 低内存地址处

(4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
因此,现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。

(5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

三、可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 例如,在C的经典教材《the c programming language》的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。

四、小结:
1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志-- printf函数就是用这个办法。后面也有例子。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.
无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。
3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。

㈢ 如何在C语言中定义参数数目可变的函数

定义如下:

<函数返回值> 函数名(形参1, 形参2, ...)

举例说明:

intfunc(inta,intb,...)
{
//函数体
......
}

printf()函数就是一个参数可变的函数,其函数原型为:

intprintf(constchar*__format,...);

㈣ c语言如何定义一个可变参数函数 如何调用其输入的参数 分别

有专门的宏,处理可变参

void va_start( va_list arg_ptr, prev_param );

type va_arg( va_list arg_ptr, type );

void va_end( va_list arg_ptr );

一个简单的例子

voidsimple_va_fun(inti,...)
{
va_listarg_ptr;
intj=0;

va_start(arg_ptr,i);
j=va_arg(arg_ptr,int);
va_end(arg_ptr);
printf("i=%dj=%dn",i,j);
return;
}
intmain()
{
simple_va_fun(1);
simple_va_fun(1,2);
simple_va_fun(1,200);
return0;
}

㈤ c语言可变参数是干什么的

可变参数是用于调用函数时,不知道参数的个数及类型的一种场合,最经典最常用的是
int
printf(const
char
*format[,
argument,
...]);
它后面的参数类型及数量都是可变的

printf("%d",x);
//一个int参数
printf("%d%d%s\n",x,y,z);
//三个参数,前两个为int,后面的为char
*

㈥ c语言 怎么写 可变 参数函数

#include<stdarg.h>//可变参数函数必要的头文件
#include<stdio.h>

longintsum(unsignedintargc,...)//可变参数函数必须包含至少一个确定的参数
{
longintsrt=0;
intva;
unsignedinti;
va_listvl;//用于指向可变参数列表
va_start(vl,argc);//初始化,va_start()调用的第一个参数是所编写的可变参数函数的最后一个确定的参数
for(i=0;i<argc;i++){
srt+=(int)va_arg(vl,int);//从参数列表按提取一个int类型的值参与相加运算
}
va_end(vl);//清理
returnsrt;
}
intmain(intargc,char*argv[])
{
printf("%ld ",sum(3,1,2,3));
return0;
}

热点内容
怎么用安卓手机查苹果的序列号 发布:2024-11-29 06:21:08 浏览:507
r11s原始密码是多少 发布:2024-11-29 05:52:20 浏览:79
c语言枚举法 发布:2024-11-29 05:50:58 浏览:125
大数据系统如何配置 发布:2024-11-29 05:48:44 浏览:89
连战访问西安小学 发布:2024-11-29 05:45:03 浏览:316
怎么编译原生安卓手机 发布:2024-11-29 05:44:28 浏览:193
java代码编译java文件 发布:2024-11-29 05:44:27 浏览:208
如何部署远程服务器 发布:2024-11-29 05:34:37 浏览:523
红米系统存储与手机存储 发布:2024-11-29 05:33:55 浏览:198
qt反编译工具 发布:2024-11-29 05:29:31 浏览:480