C++函数可变参数实现原理探究——以实现printf为例

当我们构建函数时,如果我们希望得到的是不定的参数,那我们应该怎么办呢?下面几个关键的macro上场:

#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
typedef char* va_list;
#define _INTSIZEOF(n)  ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap)  (ap = (va_list)0)
对于很多人来讲,初读这几条macro,实在会有些摸不着头脑的感觉,不过在复杂的宏也都是简单的文本替换,所以一下子松了口气。

                                                                                                                                                

#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v)
注:reinterpret 重新解释的意思,reinterpret_cast<T>可用在任意指针(或引用)类型之间的转换,以及指针与足够大的整数类型之间的转换,从整数类型(包括枚举类型)到指针类型,这里使用是为了取出(char *)类型的指针,而使用引用是防止新对象的产生。  这个macro我们可以这样用: char *p = _ADDRESSOF(s);取出char *指针指向s的地址。


#define _INTSIZEOF(n)  ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
注:这个macro我特别喜欢,在不知不觉中,你会发现这一行代码会带来这么奇妙的东西。

(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) 假若sizeof(int) = 4 ,sizeof(n) =3,则简化运算为:7 & ~(3) = 4,即 0111b & 1100b = 0100b ,这个处理方式自动处理掉小于size(int) 的位,故得运算结果始终会为 sizeof(int) 的倍数


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

注:va_start 的v指向固定参数的最后一个参数,并取得地址通过v参数的大小,计算出第一个可变参数的地址。


#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
注:va_arg的t是参数类型,粗略一看,嗯,我们可以用这个macro得到我们想要的所有类型的可变参数,于是愉快的写出了这样的代码:  char value = va_arg(ap,char);   或者这样写:  float value = va_arg(ap,float);  ,确实好像也没什么错误,恰好这就大错特错了:

C标准对于不带原型声明的函数的参数会有“默认实际参数提升(default argument promotions)"

即:

float -> double 

char,short和对应的unsigned,signed ->int 

int如果不足够大, Int -> unsigned

所以我们的t参数根本不应该使用: float, char,short 以及相关unsigned,signed。

那我们如何取出char ,float呢?

char value = (char)va_arg(ap,int);

float value =(float)va_arg(ap, double);

如果我们需要取出一个char str[] = "I'm Jovi",怎么办呢?

当然,我们了解到,函数传参str实际上是传首地址,所以依旧可以传入一个int类型获得参数大小(指针类型也可以)

即 char *str = (char *)va_arg(ap,int);

#define va_end(ap)  (ap = (va_list)0)
注: ap不再指向堆栈,指向一个0指针,相当于NULL。


这也就是几个基本macro的说明,下面我们来使用并写出自己的printf函数。

#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
typedef char* va_list;
#define _INTSIZEOF(n)  ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap)  (ap = (va_list)0)

int __cdecl MyPrintf(const char *fmt, ...)
{
	va_list vaptr;
	va_start(vaptr, fmt);
	while (*fmt)
	{
		if (*fmt == '%')
		{
			switch (*++fmt)
			{			
			case 'c':
			{
				char value;
				//value = va_arg(vaptr, char);  错误 在可变长参数中,应用的是"加宽"原则。也就是float类型被扩展成double;char, short被扩展成int。
				value = (char)va_arg(vaptr, int);
				putchar(value);
				break;
			}
			case 'd':
			{
				int value;
				char buf[12];
				char *pBuf = buf;
				value = va_arg(vaptr, int);
				if (value < 0)
				{
					value = -value;
					buf[0] = '-';
					pBuf = buf + 1;
				}

				int len = 1;
				int temp = value;
				while (temp / 10)
				{
					temp /= 10;
					len++;
				}
				temp = value;
				*(pBuf + len) = 0;

				while (len)
				{
					int t = temp % 10;
					*(pBuf - 1 + len--) = t + '0';
					temp /= 10;
				}
				fputs(buf, stdout);
				break;
			}
			case 'f':
			{
				float value;
				char buf[32];
				char *pBuf = buf;
				value = (float)va_arg(vaptr, double);
				if (value < 0)
				{
					value = -value;
					buf[0] = '-';
					pBuf = buf + 1;
				}

				//处理整数位
				int intergerlen = 1;
				int  integer = (int)value;
				while (integer / 10)
				{
					integer /= 10;
					intergerlen++;
				}
				integer = (int)value;

				int len = intergerlen;
				while (len)
				{
					int t = integer % 10;
					*(pBuf - 1 + len--) = t + '0';
					integer /= 10;
				}

				*(pBuf + intergerlen) = '.';
				pBuf = pBuf + intergerlen + 1;
				//处理小数位
				float decimals = value - (int)value;
				//8位小数
				decimals *= 100000000;
				int decimalslen = 8;
				int  int_decimals = (int)decimals;
				*(pBuf + decimalslen) = 0;
				while (decimalslen)
				{
					int t = int_decimals % 10;
					*(pBuf - 1 + decimalslen--) = t + '0';
					int_decimals /= 10;
				}
				fputs(buf, stdout);
				break;
			}
			case 's':
			{
				char *pValue;
				//此处传参为地址 lea eax,[str]
				pValue = (char *)va_arg(vaptr, int);
				fputs(pValue, stdout);
				break;
			}
			default:
				break;
			}
		}
		else
		{
			putchar(*fmt);
		}
		++fmt;
	}
	va_end(vaptr);
	return 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	int a = -120;
	char b = 'a';
	char str[] = "I'm Jovi";
	float f = 123.1415f;
	MyPrintf("输出为:a=%d , b=%c , str=%s , f=%f ",a,b,str,f);
	getchar();
	return 0;
}


运行效果:



当然,实际的printf返回值是打印出字符个数,这里我为了简化,在代码中只返回了1。

简单实现了 %d,%c,%s,%f 的输出,当然对于可变参数函数实现还有很多,比如max(int n, ...)等等,不定参数定义时须使用"..."。运用va_start,va_arg,va_end实现对可变参数的访问,将可变参数一一取出,进行处理。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值