网络推荐



本广告位招租!

推荐给好友 上一篇 | 下一篇

C中可变参数函数实现

BSD爱好者乐园 j@t7GH w?

BSD爱好者乐园&S O1NX,V
一、 从printf()开始

la.Q*A0As

6CA$RXL\ Jsv原型:int printf(const char * format, ...);BSD爱好者乐园7v:n)q'joj;H2kQ gN
参数format表示如何来格式字符串的指令,…BSD爱好者乐园'|7S*IxQu-u6CD
表示可选参数,调用时传递给"..."的参数可有可无,根据实际情况而定。BSD爱好者乐园b@$g T;@XPaG
系统提供了vprintf系列格式化字符串的函数,用于编程人员封装自己的I/O函数。BSD爱好者乐园Ldb|a WiwI0}

7Ik%|hd_(}int vprintf / vscanf(const char * format, va_list ap); // 从标准输入/输出格式化字符串
igD0vHh? [%TNi2Tint vfprintf / vfsacanf(FILE * stream, const char * format, va_list ap); // 从文件BSD爱好者乐园_T)M9^0i~
int vsprintf / vsscanf(char * s, const char * format, va_list ap); // 从字符串BSD爱好者乐园[IR#T%z3@9i8Y$l

1N2wA @"^:mHb\// 例1:格式化到一个文件流,可用于日志文件

/\ E+au| EUp

.@5a3Tat&w}U#ZmFILE *logfile;BSD爱好者乐园?"T3R pCk.e
int WriteLog(const char * format, ...)
P"k^*\&lx4bz{BSD爱好者乐园;B7iis"V"d&K;D
va_list arg_ptr;

K`f.d&}R&{2E

$?oL:]G'Pva_start(arg_ptr, format);BSD爱好者乐园Sp*^ _j
int nWrittenBytes = vfprintf(logfile, format, arg_ptr);
m!Ld&ge8`SfxWva_end(arg_ptr);

X3e;K~C9I? cBSD爱好者乐园j+G'R7Y3G0X _i2H

return nWrittenBytes;BSD爱好者乐园kY;y2f7i&a&u
}

&c] CVbc?x6S,WBSD爱好者乐园`@.h`8Ej*u


!kO/Ot0{b2V二、 va函数的定义和va宏BSD爱好者乐园wa_5G2u1w]#FD8B
    C语言支持va函数,作为C语言的扩展--C++同样支持va函数,但在C++中并不推荐使用,C++引入的多态性同样可以实现参数个数可变的函数。不过,C++的重载功能毕竟只能是有限多个可以预见的参数个数。比较而言,C中的va函数则可以定义无穷多个相当于C++的重载函数,这方面C++是无能为力的。va函数的优势表现在使用的方便性和易用性上,可以使代码更简洁。C编译器为了统一在不同的硬件架构、硬件平台上的实现,和增加代码的可移植性,提供了一系列宏来屏蔽硬件环境不同带来的差异。

-E*]c(|"J0M4vBSD爱好者乐园T+hJ:u F@i

ANSI C标准下,va的宏定义在stdarg.h中,它们有:va_list,va_start(),va_arg(),va_end()。BSD爱好者乐园I:T$\6nMB`+b-L

"RLE/s*|7s9|{:D三、 编译器如何实现va

G,N&IlH0Q(INE/|G4V-uBSD爱好者乐园,qD5GF6c9G&q:m

 简单地说,va函数的实现就是对参数指针的使用和控制。BSD爱好者乐园I~ U3X#e rWO.Y0N
typedef char *  va_list;  // x86平台下va_list的定义 BSD爱好者乐园,S^c a0T'r;s Y

6@gDL-ZU函数的固定参数部分,可以直接从函数定义时的参数名获得;对于可选参数部分,先将指针指向第一个可选参数,然后依次后移指针,根据与结束标志的比较来判断是否已经获得全部参数。因此,va函数中结束标志必须事先约定好,否则,指针会指向无效的内存地址,导致出错。

@J;l| B*t[;WC}udBSD爱好者乐园;?uf{"zGr0M$t

这里,移动指针使其指向下一个参数,那么移动指针时的偏移量是多少呢,没有具体答案,因为这里涉及到内存对齐(alignment)问题,内存对齐跟具体使用的硬件平台有密切关系,比如大家熟知的32位x86平台规定所有的变量地址必须是4的倍数(sizeof(int) = 4)。va机制中用宏_INTSIZEOF(n)来解决这个问题,没有这些宏,va的可移植性无从谈起。
PwL MK.Nu首先介绍宏_INTSIZEOF(n),它求出变量占用内存空间的大小,是va的实现的基础。

3w(e"m WN

5v#x9A fP4kOKBSD爱好者乐园eBG S Ko}
#define _INTSIZEOF(n)  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )BSD爱好者乐园w-B+}6t"Rc

)RM"C4s3n;qBSD爱好者乐园9[ r5}(C9wbN @^
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )          //第一个可选参数地址BSD爱好者乐园"`gDdh9^a

BSD爱好者乐园"d"q(n!J\*j


q%e9q @/N3B+Fv6L'jn#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址

3H!GQu |t1y

.T`/G1[/h,`6yBSD爱好者乐园e%`!CX z;{&`
#define va_end(ap)   \BSD爱好者乐园Q_X[twDG,fm

BSD爱好者乐园q'H*tEC7Lyb

( ap=va_list0 )                           // 将指针置为无效BSD爱好者乐园%C ^ [8TE+He x7Z

BSD爱好者乐园qV[ \JT{

BSD爱好者乐园S-L#C2RT
1.va_arg身兼二职:返回当前参数,并使参数指针指向下一个参数。BSD爱好者乐园 ts9I S#B Uo'| F

BSD爱好者乐园5Mn A ~8g6leS4?y

初看va_arg宏定义很别扭,如果把它拆成两个语句,可以很清楚地看出它完成的两个职责。BSD爱好者乐园$j m$Z |'Y
#define va_arg(ap,t)   ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址BSD爱好者乐园w:JORij
// 将( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )拆成:BSD爱好者乐园:H\\tR&^ b
/* 指针ap指向下一个参数的地址 */BSD爱好者乐园;t]d-?nycT q t
1). ap += _INTSIZEOF(t);        // 当前,ap已经指向下一个参数了
&X1\vwWi b.V/* ap减去当前参数的大小得到当前参数的地址,再强制类型转换后返回它的值 */BSD爱好者乐园7y8hm;b1x3^8g1c2J
2). return *(t *)( ap - _INTSIZEOF(t))BSD爱好者乐园y;C| W!^p4C6Y
回想到printf/scanf系列函数的%d %s之类的格式化指令,我们不难理解这些它们的用途了- 明示参数强制转换的类型。
c`E;C%f;Z"^W(注:printf/scanf没有使用va_xxx来实现,但原理是一致的。)

Qi3WV(rai

'cz~D.z-TlUBSD爱好者乐园 T|%T0q3ovi
2.va_end很简单,仅仅是把指针作废而已。
ss-G+Q+_5? }MO3o#define va_end(ap) (ap = (va_list)0) // x86平台BSD爱好者乐园/c.C7xk D6HCi G


[重要提醒]对本篇资料有疑问,请到论坛讨论,尽量使文章准确无误>>>
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
 

评分:0

我来说两句

seccode