风流倜傥、什么是可变参数
小编们在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语言编制程序中会遭受有的参数个数可变的函数,比方printf(卡塔尔

*二、可变参数在编写翻译器中的管理
咱俩知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,
由于1卡塔尔硬件平台的例外
2卡塔尔国编写翻译器的例外,所以定义的宏也可以有所分歧,下边看一下VC++6.0中stdarg.h里的代码(文件的渠道为VC安装目录下的vc98澳门新葡萄京官网首页,includestdarg.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 )

上边大家来商讨怎么着写二个简单的可变参数的C函数.写可变参数的
C函数要在前后相继中用到以下这几个宏:
void va_start( va_list arg_ptr, prev_param );

那一个函数,它的概念是那般的:

上边大家解说这几个代码的含义:

type va_arg( va_list arg_ptr, type );

int printf( const char* format, …);

1、首先把va_list被定义成char*,这是因为在大家当下所用的PC机上,字符指针类型能够用来囤积内存单元地址。而在局地机器上va_list是被定义成void*的

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;

它除了有二个参数format固定以外,前面跟的参数的个数和种类是

2、定义_INTSIZEOF(n卡塔尔国重若是为了一点需求内部存款和储蓄器的对齐的系统.那几个宏的目标是为着拿走最后一个一定参数的实际上内部存款和储蓄器大小。在本身的机器上平昔用sizeof运算符来替代,对程序的运作构造也未有影响。(后文将见到自个儿自个儿的兑现)。

va_start(arg_ptr, i);
j=va_arg(arg_ptr, int);
va_end(arg_ptr);
printf(“%d %d “, 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的第1个
参数是你要回来的参数的项目,这里是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
笔者们看到第生龙活虎种调用有怪诞,第三种调用正确,第三种调用尽管结果
不容争辩,但和我们函数最先的统筹有冲突.上边生龙活虎节我们索求现身这几个结果
的原因和可变参数在编写翻译器中是何许处理的.

可变的,举个例子大家得以有以下两样的调用方法:

3、va_start的定义为 &v+_INTSIZEOF(vState of Qatar,这里&v是最后三个永远参数的开局部址,再加上其实际占领大小后,就获得了第叁个可变参数的开场内部存款和储蓄器地址。所以我们运转va_start(ap,
v卡塔尔(قطر‎以往,ap指向第贰个可变参数在的内部存款和储蓄器地址,有了这几个地方,今后的作业就简单了。

(二卡塔尔国可变参数在编写翻译器中的管理

printf(“%d”,i);

此地要明了四个事情:
      
⑴在intel+windows的机器上,函数栈的趋势是向下的,栈顶指针的内部存款和储蓄器地址低于栈底指针,所以先进栈的多少是存放在内存的高地址处。
      
(2卡塔尔(قطر‎在VC等绝大好些个C编写翻译器中,私下认可情况下,参数进栈的逐条是由右向左的,因而,参数进栈未来的内部存款和储蓄器模型如下图所示:尾数固定参数之处坐落于第一个可变参数之下,而且是接连存款和储蓄的。
|————————–|
|     最终四个可变参数                |      ->高内部存款和储蓄器地址处
|————————–|
|————————–|
|     第N个可变参数                 |       
->va_arg(arg_ptr,int)后arg_ptr所指之处,
|                                  |        即第N个可变参数之处。
|————— |     
|————————–|
|     第叁个可变参数                  |       
->va_start(arg_ptr,start)后arg_ptr所指的地点
|                                  |        即首先个可变参数之处
|————— |     
|———————— –|
|                                  |
|     最后一个恒久参数                |       -> start的发端地址
|————– -|          ……………..
|————————– |
|                                  |  
|————— |     -> 低内部存款和储蓄器地址处

咱俩驾驭va_start,va_arg,va_end是在stdarg.h中被定义成宏的,
鉴于1卡塔尔硬件平台的两样 2卡塔尔(قطر‎编写翻译器的不等,所以定义的宏也可能有所不一致,下
面以VC++中stdarg.h里x86阳台的宏定义摘录如下(’’号表示折行卡塔尔:

printf(“%s”,s);

(4)
va_arg():有了va_start的卓绝底蕴,大家获得了第一个可变参数的地点,在va_arg(卡塔尔国里的天职正是基于内定的参数类型拿到本参数的值,而且把指针调到下三个参数的胚胎地址。
因此,以后再来看va_arg(卡塔尔的落到实处就活该胸有成竹了:
       #define va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) –
_INTSIZEOF(t)) )
那一个宏做了三个事情,
     
①用客户输入的品种名对参数地址进行强逼类型调换,拿到客商所须求的值
     
②计量出本参数的莫过于尺寸,将指针调到本参数的最终,也正是下多少个参数的首地址,以便后续管理。

typedef char * va_list;

printf(“the number is %d ,string is:%s”, i, s);

(5)va_end宏的解释:x86平台定义为ap=(char*卡塔尔国0;使ap不再
指向旅馆,而是跟NULL相仿.有个别直接定义为((void*卡塔尔(قطر‎0卡塔尔国,那样编写翻译器不会为va_end发生代码,举例gcc在linux的x86平台就是这么定义的.
在那地大家要静心一个主题素材:由于参数的地点用于va_start宏,所以参数不能够声称为存放器变量或当做函数或数组类型.
关于va_start, va_arg, va_end的叙说正是那一个了,大家要细心的
是差别的操作系统和硬件平台的定义某些区别,但原理却是相近的.**

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

终归什么写可变参数的C函数以至这么些可变参数的函数编写翻译器是什么实

**三、可变参数在编制程序中要注意的主题素材
因为va_start, va_arg, va_end等定义成宏,所以它显得很鲁钝,
可变参数的门类和个数完全在该函数中由程序代码调控,它并不能够智能
地辨识不一样参数的个数和类型.
有人会问:那么printf中不是得以落成了智能识别参数吗?那是因为函数
printf是从固定参数format字符串来剖判出参数的项目,再调用va_arg
的来获得可变参数的.也正是说,你想实现智能识别可变参数的话是要通过在和谐的主次里作推断来落实的.
举例,在C的优异教材《the c programming
language》的7.3节中就付出了二个printf的只怕达成情势,由于篇幅原因这里不再陈诉。

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

现的吧?本文就以此标题张开一些探寻,希望能对我们不怎么扶助.会C++的

四、小结:
1、标准C库的中的八个宏的效果只是用来规定可变参数列表中种种参数的内存地址,编写翻译器是不知底参数的骨子里多少的。
2、在事实上行使的代码中,程序员必需本身思谋鲜明参数数指标诀要,如
⑴在定位参数中设标识– printf函数就是用那个办法。后边也可能有例子。
⑵在先行设定三个非同小可的了断标识,正是说多输入三个可变参数,调用时要将倒数可变参数的值设置成那几个杰出的值,在函数体中依据这几个值判定是或不是抵达参数的最终。本文后边的代码正是行使这么些办法.
随意使用哪个种类艺术,程序猿都应当在文书档案中报告调用者自身的约定。
3、实现可变参数的焦点正是想方法得到每种参数的地点,拿到地点的章程由以下多少个要素决定:
①函数栈的发育方向
②参数的入栈顺序
③CPU的对齐方式
④内部存款和储蓄器地址的表明情势
重新整合源代码,大家得以看到va_list的实现是由④操纵的,_INTSIZEOF(n卡塔尔(قطر‎的引进则是由③调整的,他和①②又一齐决定了va_start的实现,最后va_end的留存则是爱不忍释编制程序风格的反映,将不再利用的指针设为NULL,那样能够免备现在的误操作。
4、拿到地点后,再结合参数的品类,技师就能够精确的拍卖参数了。精晓了以上要点,相信稍有阅历的读者就能够写出切合于本身机器的兑现来。**

#define va_arg(ap,t)
( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) )

网民理解那个题目在C++里不设有,因为C++拥有多态性.但C++是C的一个

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

超集,以下的手艺也足以用来C++的前后相继中.限于小编的水准,文中若是有

定义_INTSIZEOF(n卡塔尔(قطر‎主要是为了一点必要内部存款和储蓄器的对齐的系统.C语言的函
数是从右向左压入酒馆的,图(1卡塔尔国是函数的参数在仓房中的布满地方.我
们看到va_list被定义成char*,有部分阳台或操作系统定义为void*.再
看va_start的定义,定义为&v+_INTSIZEOF(vState of Qatar,而&v是恒久参数在仓房的
地点,所以大家运行va_start(ap, vState of Qatar现在,ap指向第三个可变参数在堆
栈之处,如图:

不当之处,请我们指正.

高地址|—————————–|
|函数重返地址 |
|—————————–|
|……………………………….|
|—————————–|
|第n个参数(第八个可变参数卡塔尔国   |
|—————————–|<–va_start后ap指向
|第n-1个参数(最后一个稳住参数卡塔尔|
低地址|—————————–|<– &v
图( 1 )

(大器晚成State of Qatar写多个简约的可变参数的C函数

然后,我们用va_arg(卡塔尔获得类型t的可变参数值,以上例为int型为例,小编
们看一下va_arg取int型的重返值:
j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
率先ap+=sizeof(int卡塔尔(قطر‎,已经指向性下七个参数之处了.然后回去
ap-sizeof(int)的int*指南针,那多亏第两个可变参数在货仓里的地点
(图2).然后用*获取这几个地点的情节(参数值State of Qatar赋给j.

下面大家来探寻怎么着写八个简便的可变参数的C函数.写可变参数的

高地址|—————————–|
|函数再次来到地址 |
|—————————–|
|……………………………….|
|—————————–|<–va_arg后ap指向
|第n个参数(第三个可变参数卡塔尔 |
|—————————–|<–va_start后ap指向
|第n-1个参数(尾数挺住参数卡塔尔(قطر‎|
低地址|—————————–|<– &v
图( 2 )

C函数要在程序中用到以下那一个宏:

终极要说的是va_end宏的意味,x86平台定义为ap=(char*)0;使ap不再
本着旅社,而是跟NULL同样.有些直接定义为((void*卡塔尔国0卡塔尔国,那样编译器不
会为va_end爆发代码,举例gcc在linux的x86平台正是这么定义的.
在这里处大家要注意五个主题素材:由于参数的地址用于va_start宏,所
以参数无法声称为寄放器变量或作为函数或数组类型.
关于va_start, va_arg, va_end的陈说正是那些了,我们要静心的
是分化的操作系统和硬件平台的定义某些差异,但原理却是相同的.

void va_start( va_list arg_ptr, prev_param );

(三卡塔尔可变参数在编制程序中要小心的标题

type va_arg( va_list arg_ptr, type );

因为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;

void va_end( va_list arg_ptr );

va_start(arg_ptr, i);
s=va_arg(arg_ptr, char*);
va_end(arg_ptr);
printf(“%d %s “, 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在那地是variable-argument(可变参数卡塔尔国的意思.

上面大家来探寻怎么着写多个粗略的可变参数的C函数.写可变参数的
C函数要在前后相继中用到以下那个宏: vo…

那个宏定义在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);

从这几个函数的得以完成能够看来,大家应用可变参数应该有以下步骤:

1State of Qatar首先在函数里定义叁个va_list型的变量,这里是arg_ptr,这个变

量是指向参数的指针.

2)然后用va_start宏起先化变量arg_ptr,这些宏的第1个参数是第

八个可变参数的前一个参数,是三个原则性的参数.

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

参数是您要赶回的参数的品种,这里是int型.

4)最后用va_end宏甘休可变参数的获得.然后您就足以在函数里使

用第1个参数了.假设函数有多少个可变参数的,依次调用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(UnixState of Qatar 恐怕页面不合规的失实(window平台State of Qatar.但也会有望不出

错,但错误却是难以觉察,不低价大家写出高素质的程序.

以下提一下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语言的法子来完成.

作品转自: