天天看點

c語言 不定參數printf的實作

定義一個不定參數的函數

int my_printf(const char *fmt, ...) 
           

不定參數存放在哪

傳遞參數是依次存放在棧中傳遞的,不定參數存放在固定參數的後面

怎麼讀取不定參數的數值

記憶體中不定參數,4位元組對齊,對于指針變量,隻儲存指針變量的指針所指位址。

可以定義一些宏來對不定參數的讀取進行操作,這些宏在後面會用到

typedef char *  va_list;
//資料類型的長度計算,四位元組對齊
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 

//把ap指針移動到v的最後面
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )  

//傳回ap内的數值,然後将ap指針後移
//#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  

//防止野指針的存在
#define va_end(ap)      ( ap = (va_list)0 )
           

函數簡單的舉例

int printf(const char *fmt, ...) 
{
	char str[110] = {0};
	uint8_t offset = 0;
	va_list ap;
	va_start(ap, fmt); 
	
	 for(; *fmt != '\0'; fmt++)
	 {
			if (*fmt != '%') {
				offset = load_c(str,offset,*fmt);
				continue;
			}
			
		 fmt++;
			
		switch(*fmt)
		{
			case 'u':
				offset = load_data(str,offset ,va_arg(ap, unsigned int));
				break;
			case 's':
				offset = load_string(str,offset ,va_arg(ap, char *));
				break;
			case 'f':
				offset = load_fdata(str,offset ,va_arg(ap, unsigned int));
				break;
			default:
				offset = load_c(str,offset,*fmt);
				break;
		}
		
		if(offset >= 100)
			break;
	}
	 
	va_end(ap);
	
  uart_send_str(str);
	
	return 0;
}
           

上面函數所調用的函數

/*
 *  value: 要轉換的整數,string: 轉換後的字元串,radix: 轉換進制數,如2,8,10,16 進制等。
 */
static char* itoa(int num,char* str,int radix)
{
	char index[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//索引表
	unsigned unum;//存放要轉換的整數的絕對值,轉換的整數可能是負數
	int i=0,j,k;//i用來訓示設定字元串相應位,轉換之後i其實就是字元串的長度;轉換後順序是逆序的,有正負的情況,k用來訓示調整順序的開始位置;j用來訓示調整順序時的交換。
 
	//擷取要轉換的整數的絕對值
	if(radix==10&&num<0)//要轉換成十進制數并且是負數
	{
		unum=(unsigned)-num;//将num的絕對值賦給unum
		str[i++]='-';//在字元串最前面設定為'-'号,并且索引加1
	}
	else unum=(unsigned)num;//若是num為正,直接指派給unum
 
	//轉換部分,注意轉換後是逆序的
	do
	{
		str[i++]=index[unum%(unsigned)radix];//取unum的最後一位,并設定為str對應位,訓示索引加1
		unum/=radix;//unum去掉最後一位
 
	}while(unum);//直至unum為0退出循環
 
	str[i]='\0';//在字元串最後添加'\0'字元,c語言字元串以'\0'結束。
 
	//将順序調整過來
	if(str[0]=='-') k=1;//如果是負數,符号不用調整,從符号後面開始調整
	else k=0;//不是負數,全部都要調整
 
	char temp;//臨時變量,交換兩個值時用到
	for(j=k;j<=(i-1)/2;j++)//頭尾一一對稱交換,i其實就是字元串的長度,索引最大值比長度少1
	{
		temp=str[j];//頭部指派給臨時變量
		str[j]=str[i-1+k-j];//尾部指派給頭部
		str[i-1+k-j]=temp;//将臨時變量的值(其實就是之前的頭部值)賦給尾部
	}
 
	return str;//傳回轉換後的字元串
}


/*
 *  str傳入的數組基礎位址,offset數組的偏移,c傳入的字元
 *  傳回填入字元後的偏移值
 */
static uint8_t load_c(char *str,uint8_t offset ,char c)
{
	* (str + offset) = c;
	offset ++;
	return offset;
}
/*
 *  str傳入的數組基礎位址,offset數組的偏移,data傳入的數字
 *  傳回填入字元後的偏移值
 */
static uint8_t load_data(char *str,uint8_t offset ,uint32_t data)
{
  char datastr[17] = {0};
	itoa(data,datastr,10);
	uint8_t len = strlen(datastr);
	
	for(uint8_t i = 0 ; i < len ; i ++)
	{
		* (str + offset) = datastr[i];
		offset ++;
	}
	return offset;
}
/*
 *  str傳入的數組基礎位址,offset數組的偏移,data傳入的數字
 *  傳回填入字元後的偏移值
 *  與load_data不同的是,這個函數會吧數字保留一位小數裝填
 */
static uint8_t load_fdata(char *str,uint8_t offset ,uint32_t data)
{
  char datastr[17] = {0};
	itoa(data,datastr,10);
	uint8_t len = strlen(datastr);
	if(len == 1)
	{
		* (str + offset++) = datastr[0]; 
		* (str + offset++) = '.';
		* (str + offset++) = '0';
	}
	else if(len > 1)
	{
			for(uint8_t i = 0 ; i < len ; i ++)
			{
				* (str + offset++) = datastr[i];
				if( i == (len - 2))
				 * (str + offset++)  = '.';
			}
	
	}
  return offset;
}

/*
 *  str傳入的數組基礎位址,offset數組的偏移,addstr傳入的字元串
 *  傳回填入字元後的偏移值
 */
static uint8_t load_string(char *str,uint8_t offset ,char *addstr)
{
	uint8_t len = strlen(addstr);
	for(uint8_t i = 0 ; i < len ; i ++)
	{
		* (str + offset) = addstr[i];
		offset ++;
	}
	return offset;
	
}
           

my_printf函數試驗

char a = 1;
char b = 21;
char str[20] = {0};
strcpy(str,"hello world!")
my_printf("a = %u , b = %f , str = %s" , a , b , str );
//輸出結果 a = 1 , b = 2.1 ,str = hello world!
           

繼續閱讀