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實現對可變參數的訪問,將可變參數一一取出,進行處理。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章