C言语中可变参数的用法-C / C++-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝29
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:228回复:0

  C言语中可变参数的用法

楼主#
更多 发布于:2016-05-21 23:05

咱们在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函数以及这些可变参数的函数编译器是怎么实

现的呢?这篇文章就这个疑问进行一些讨论,期望能对咱们有些帮助.会C++的

网友知道这些疑问在C++里不存在,由于C++具有多态性.但C++是C的一个

超集,以下的技术也能够用于C++的程序中.限于自个的水平,文中假如有

不当之处,请咱们纠正.

(一)写一个简略的可变参数的C函数

下面咱们来讨论怎么写一个简略的可变参数的C函数.写可变参数的

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 );

va在这里是variable-argument(可变参数)的意思.

这些宏界说在stdarg.h中,所以用到可变参数的程序应当包含这个

头文件.下面咱们写一个简略的可变参数的函数,改函数至少有一个整数

参数,第二个参数也是整数,是可选的.函数仅仅打印这两个参数的值.

void simple_va_fun(int i, ...)

{

va_list arg_ptr;

int j=0;

va_start(arg_ptr, i);

j=va_arg(arg_ptr, int);

va_end(arg_ptr);

printf("%d %d\n", i, j);

return;

}

咱们能够在咱们的头文件中这么声明咱们的函数:

extern void simple_va_fun(int i, ...);

咱们在程序中能够这么调用:

simple_va_fun(100);

simple_va_fun(100,200);

从这个函数的完成能够看到,咱们运用可变参数应当有以下过程:

1)首先在函数里界说一个va_list型的变量,这里是arg_ptr,这个变

量是指向参数的指针.

2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第

一个可变参数的前一个参数,是一个固定的参数.

3)然后用va_arg回来可变的参数,并赋值给整数j. va_arg的第二个

参数是你要回来的参数的类型,这里是int型.

4)最终用va_end宏完毕可变参数的获取.然后你就能够在函数里使

用第二个参数了.假如函数有多个可变参数的,顺次调用va_arg获

取各个参数.

假如咱们用下面三种办法调用的话,都是合法的,但成果却不相同:

1)simple_va_fun(100);

成果是:100 -123456789(会变的值)

2)simple_va_fun(100,200);

成果是:100 200

3)simple_va_fun(100,200,300);

成果是:100 200

咱们看到第一种调用有过错,第二种调用准确,第三种调用虽然成果

准确,但和咱们函数开始的设计有抵触.下面一节咱们讨论呈现这些成果

的因素和可变参数在编译器中是怎么处理的.

(二)可变参数在编译器中的处理

咱们知道va_start,va_arg,va_end是在stdarg.h中被界说成宏的,

由于1)硬件渠道的不相同 2)编译器的不相同,所以界说的宏也有所不相同,下

面以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 )

界说_INTSIZEOF(n)首要是为了某些需求内存的对齐的体系.C言语的函

数是从右向左压入仓库的,图(1)是函数的参数在仓库中的散布方位.我

们看到va_list被界说成char*,有一些渠道或操作体系界说为void*.再

看va_start的界说,界说为&v+_INTSIZEOF(v),而&v是固定参数在仓库的

地址,所以咱们运转va_start(ap, v)今后,ap指向第一个可变参数在堆

栈的地址,如图:

高地址|-----------------------------|

|函数回来地址 |

|-----------------------------|

|....... |

|-----------------------------|

|第n个参数(第一个可变参数) |

|-----------------------------|<--va_start后ap指向

|第n-1个参数(最终一个固定参数)|

低地址|-----------------------------|<--

&v

图( 1 )

然后,咱们用va_arg()获得类型t的可变参数值,以上例为int型为例,我

们看一下va_arg取int型的回来值:

j= ( *(int*)((ap +=

_INTSIZEOF(int))-_INTSIZEOF(int)) );

首先ap+=sizeof(int),现已指向下一个参数的地址了.然后回来

ap-sizeof(int)的int*指针,这正是第一个可变参数在仓库里的地址

(图2).然后用*获得这个地址的内容(参数值)赋给j.

高地址|-----------------------------|

|函数回来地址 |

|-----------------------------|

|....... |

|-----------------------------|<--va_arg后ap指向

|第n个参数(第一个可变参数) |

|-----------------------------|<--va_start后ap指向

|第n-1个参数(最终一个固定参数)|

低地址|-----------------------------|<--

&v

图( 2 )

最终要说的是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

的来获取可变参数的.也即是说,你想完成智能辨认可变参数的话是要通

过在自个的程序里作判断来完成的.

别的有一个疑问,由于编译器对可变参数的函数的原型查看不行严

格,对编程查错晦气.假如simple_va_fun()改为:

void simple_va_fun(int i, ...)

{

va_list arg_ptr;

char *s=NULL;

va_start(arg_ptr, i);

s=va_arg(arg_ptr, char*);

va_end(arg_ptr);

printf("%d %s\n", i, s);

return;

}

可变参数为char*型,当咱们忘掉用两个参数来调用该函数时,就会呈现

core dump(Unix) 或许页面不合法的过错(window渠道).但也有也许不出

错,但过错却是难以发现,晦气于咱们写出高质量的程序.

以下提一下va系列宏的兼容性.

System V Unix把va_start界说为只要一个参数的宏:

va_start(va_list arg_ptr);

而ANSI C则界说为:

va_start(va_list arg_ptr, prev_param);

假如咱们要用system V的界说,应当用vararg.h头文件中所界说的

宏,ANSI C的宏跟system V的宏是不兼容的,咱们通常都用ANSI C,所以

用ANSI C的界说就够了,也便于程序的移植.

小结:

可变参数的函数原理本来很简略,而va系列是以宏界说来界说的,实

现跟仓库有关.咱们写一个可变函数的C函数时,有利也有弊,所以在不用

要的场合,咱们无需用到可变参数.假如在C++里,咱们应当利用C++的多

态性来完成可变参数的功用,尽量避不用C言语的方法来完成.

优质IT资源分享社区[font=Tahoma  ]为你提供此文。[font=Tahoma  ]

[font=Tahoma  ]本站有大量优质C、C++教程视频,资料等资源,包含C,C++基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。[font=Tahoma  ]

C、C++教程视频

优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客