不知在座各位有没有想过,为什么printf,scanf的函数参数总是不定长呢?这是“惯例”?恐怕不是,我以前倒想过,解决方案是void*列表(笑),但完全无法用,遂弃。到后来深入了解C语言时,终于发现了stdargs.h和varargs.h(已弃用)这些宏库。凭借着{...}变参语法和自己对C语言的了解,简单地搓了一个变参宏Vaget。分析流程和代码实例见文末。
// Link Here: //
typedef char* va_lst;
#define va_mcl ...
#define va_start(val,mod) ((char*)(&val)+sizeof(mod))
#define va_get(va,mod) (*((mod*)va))
#define va_pas(va,mod) (va+=sizeof(mod),0)
// LnkEnd //
上面的Vaget实现包含了一组宏和一个typedef将char*包装,宏va_mcl作为函数参数需放在列表的末尾,宏va_start启用参数列表,va_get获得mod类型的参数,va_pas用来取得宏列表的下一项
以上便是对这组宏的简析了。
分析流程
首先让我们做一个小小的假定,所有参数的内存地址是连续的,也就是说,foo(int a,int b,int c),则内存分配如下(每一个【:】都代表一字节)
|::::|::::|::::| 12 Bytes
a b c
// 当sizeof(int)为(size_t)4时
那么,我们只需要知道a的内存地址,就能推断出b,c的内存地址,自然而然能取其值了。
// 当a的内存地址为0x70时
a: 0x70
b: 0x74 = a+sizeof(int)
c: 0x78 = a+sizeof(int)+sizeof(int) = b+sizeof(int)
像这样,当a为double类型时,b就为a+sizeof(double)
也就是说,只要用指针取值并依次递增对应字节数,就可以很方便优雅地取得参数了
为了让我们的函数可以读入多个参数而不报错,需要用stdargs.h+ANSI C标准提供的多参语法{...}
即函数应为foo(int sum,...)即可读入至少一个的不同类型参数了(至少读入硬要求的(int)sum)
注:在最开始varargs.h被实现时并没有{...}语法,所以它的部分实现采用了老式C编译器的特性:根据上下文规划未声明函数的返回值和参数列表,使用va_dcl宏来表示多参数
代码实例
#include <stdio.h>
// Link Here: //
typedef char* va_lst;
#define va_mcl ...
#define va_start(val,mod) ((char*)(&val)+sizeof(mod))
#define va_get(va,mod) (*((mod*)va))
#define va_pas(va,mod) (va+=sizeof(mod),0)
// LnkEnd //
void cprint(const char*fmt, va_mcl) {
va_lst vst=va_start(fmt,char*);
for(int i = 0; i < strlen(fmt); i++) {
if(fmt[i]=='!') {
printf("%d",va_get(vst,int)),va_pas(vst,int);
} else {
putchar(fmt[i]);
}
}
return ;
}
*上面代码仅提供示例,cprint函数的[!]类如printf的%d,但是只能用[!]转义符,并不完善*
运行环境TurboC++ 3.0,运行操作系统DOSBox模拟的DOS系统,运行结果良好(请将文件存为.CPP档案之后才能正确运行)
Vaget和<varargs.h>弊端分析
Vaget和<varargs.h>采用了同样的原理,通过内存存储的参数列表来读取,但它们始终有着一个很致命的弊端:类型转换。冷僻的不完整类型参数会被转化为完整类型,例如char,short转化为int,float转化为double,所以va_get和va_pas不得使用这些类型而需使用转化后类型,同时一些自定义结构可能无法被转化。
摘抄一段CTP中提到的<varargs.h>实现:
typedef char *va_list;
#define va_dcl int va_alist;
#define va_start(list) list=(char *)&va_alist
#define va_end(list)
#define va_arg(list,mode) \
((mode*) (list += sizeof(mode)))[-1]
因为不被部分编译器支持,请尽量使用ANSI C标准规定的stdargs.h和{...}语法。