Vaget - <varargs.h>自实现+分析流程+代码实例

不知在座各位有没有想过,为什么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和{...}语法。

参考文献:《C陷阱与缺陷》(C Traps and Pitfalls, Andrew Koenig, 译者 高巍)

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值