嵌入式操作系统---打印函数(printf/sprintf)的实现

格式化输出函数:printf/sprintf/fprintf/snprintf等等

一、打印函数简介

作用:将“给定的内容”按照“指定的格式”输出到“指定目标内”。

打印函数的基本格式:

char print_buf[BUF_SIZE];
void printf(const char *fmt, ...)
{
    va_list ap;//定义一个指针变量
    unsigned int i;

    va_start (ap, fmt);
    i = vsprintf (print_buf, sizeof(print_buf),fmt, ap);
    va_end (args);

    __put_char (print_buf,i);
}

printf(const char *fmt,...)是一个可变参数函数,第一个参数为字符串,后面是格式化输出参数列表。
c语言中函数的参数都是压进栈里的,可变参数函数必须有一个参数表示参数的个数,才能让编译器知道要压进栈多少参数,以及函数返回时弹出多少参数,printf(char *fmt,...)实现这个功能的是fmt字符串,里面有多少'%',就代表后面有多少个参数,所以我们必须提取出fmt字符串中'%'的个数,以及针对'%'后面不同的字符来处理参数。

const char *fmt定义了一个只读字符指针;“...”表示printf的参数是可变的;

va_list ap:定义一个字符型指针变量

va_start(argv,i):初始化argv

c=va_arg(argv,int):在已知变量的情况下,获得下一个变参变量

va_end(argv):结束变参变量操作

其中,__put_char()将字符逐个打印到串口输出寄存器中。

void __put_char(char *p,int num){
while(*p&&num--){
*(volatile unsigned int *)0xd0000020=*p++;
};
}

二、打印函数的实现

1、变参函数的实现(宏定义)

(1)va_list

typedef char * va_list;
va_list       ap;        //定义一个指针类型

(2)va_start(ap,fmt) 

#define va_start(ap,fmt) ( ap = (va_list)&fmt + _INTSIZEOF(fmt) )

ap指向函数栈中变参列表第一个参数的首地址;

____________________________
|___________________________|
|                      argn                              |
|                      ........                              |
|                      arg0                              |<----ap(sizeof(int)大小对齐)
|                      fmt                                 |
|                      pc                                  |
|___________________________|

注意:传给宏va_start的参数fmt是可变参数列表中的前一个参数,ap指向变参列表中第一个参数地址。
注意:函数参数压栈时,参数的入栈顺序是从右向左,出栈时是从左向右。函数调用时,先把若干个参数都压入栈中,再压fmt,最后压pc,这样一来,栈顶指针偏移便找到了fmt,通过fmt中的%占位符,取得后面参数的个数,从而正确取得所有参数。

#define _INTSIZEOF(n)   ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

计算int类型按4字节(int占4字节)对齐后的结果。通过使用_INTSIZEOF(n),可以根据一个变量的类型计算变量在内存中占用的字节数,从而正确定位参数在内存的位置。

对于short、char类型的数据,因为不满一个int类型的内存空间,所以按照int类型对齐;

(3)va_arg(ap,type)

#define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )

指针变量ap本身指向变参列表中的下一个参数地址,va_arg取出的是当前参数的值

(4)va_end(ap)

#define va_end(ap)    ( ap = (va_list)0 )

ap指向空

 

2、vsprintf(char *buf, const char *fmt, va_list args)函数实现

函数功能:将变量列表args中的参数按照fmt中规定的格式保存到临时缓存buf中。

int vsprintf(char *buf, canst char *fmt, va_list args) 
{
	unsigned NUM_TYPE num;
	int base;
	char*str;
	int flags;
	int field_width;
	int Precision;
	int qualifier;
	str = bUf;

	for (; *fmt ; ++fmt) 
	{
		if (* fmt ! = ' % ' )
		{
			*str++ = *fmt;
			continue;
		}

		/* process flags */
		flags = 0;
		repeat:
		++fmt;/* skip first "%" */

		switch(*fmt)
		{
			case '- ' : flags |= LEFT;goto repeat;
			case '+ ' : flags |= PLUS;goto repeat;
			...
		}
		...
		base = 10;

		switch (*fmt){
		case 'c':
			...
			*str++ (unsigned char)va_arg(args,int);
			...
		continue;


		case 's':
			str = string(str,va_arg(args, char *),field_width,precision,flags);
		continue;
		...

		case ' X ' :
			base = 16;
		break;

		case 'd':


		case ' i '
			flags |= SIGN;

		case 'u':
			break;

		default:
			* str++ ='%';
		if (*fmt)
			*str++ = *fmt;
		else
			--fmt ;
			continue;
		}
		str = number (str, num, base, field_width, precision, flags) ;

	}
	*str == '\0';

	return str-buf;
}

三、实现自己的打印函数

int vsnprintf(char *buf, int size, const char *fmt, va_list args){
	int num;
	char *str, *end, c,*s;
	int read;
	unsigned int spec=0;

	str = buf;//临时缓存buf首地址
	end = buf + size;//临时缓存buff结束地址

	if (end < buf) {
		end = ((void *)-1);
		size = end - buf;
	}

	while (*fmt) {
		const char *old_fmt = fmt;//保存原来fmt的格式的首地址

		read = format_decode(fmt, &spec);//判断参数的格式,保存到spec中,read为当前参数在字符串中的指针偏移
		fmt += read;//指针偏移到本参数格式的下一位字符的地址

		if((FORMAT_TYPE(spec))==FORMAT_TYPE_NONE){
			int copy = read;
			if (str < end) {
				if (copy > end - str)//防止buff空间不足越界
					copy = end - str;
					memcpy(str, old_fmt, copy);//原样拷贝到buff中
			}
			str += read;//更新字符偏移


		}else if(spec&FORMAT_FLAG_WIDTH){
			//do nothing
		}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_CHAR){//字符类型,直接拷贝
			c = (unsigned char) va_arg(args, int);
			if (str < end)
				*str = c;
			++str;
		}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_STR){//字符串类型,直接拷贝
			s = (char *) va_arg(args, char *);
			while(str<end&&*s!='\0'){
				*str++=*s++;
			}
		}else{//数值型,进行转换
			if(FORMAT_TYPE(spec)==FORMAT_TYPE_INT){
				num = va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_ULONG){
				num = va_arg(args, unsigned long);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_LONG){
				num = va_arg(args, long);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_SIZE_T){
				num = va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_USHORT){
				num = (unsigned short) va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_SHORT){
				num = (short) va_arg(args, int);
			}else{
				num = va_arg(args, unsigned int);
			}
				str=number(str,num,spec&FORMAT_BASE_MASK,spec);
		}
	}
	if (size > 0) {
		if (str < end)
			*str = '\0';
		else
			end[-1] = '\0';
	}
	return str-buf;
}format_decode(fmt, &spec);//判断参数的格式,保存到spec中,read为当前参数在字符串中的指针偏移
		fmt += read;//指针偏移到本参数格式的下一位字符的地址

		if((FORMAT_TYPE(spec))==FORMAT_TYPE_NONE){
			int copy = read;
			if (str < end) {
				if (copy > end - str)//防止buff空间不足越界
					copy = end - str;
					memcpy(str, old_fmt, copy);//原样拷贝到buff中
			}
			str += read;//更新字符偏移


		}else if(spec&FORMAT_FLAG_WIDTH){
			//do nothing
		}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_CHAR){//字符类型,直接拷贝
			c = (unsigned char) va_arg(args, int);
			if (str < end)
				*str = c;
			++str;
		}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_STR){//字符串类型,直接拷贝
			s = (char *) va_arg(args, char *);
			while(str<end&&*s!='\0'){
				*str++=*s++;
			}
		}else{//数值型,进行转换
			if(FORMAT_TYPE(spec)==FORMAT_TYPE_INT){
				num = va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_ULONG){
				num = va_arg(args, unsigned long);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_LONG){
				num = va_arg(args, long);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_SIZE_T){
				num = va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_USHORT){
				num = (unsigned short) va_arg(args, int);
			}else if(FORMAT_TYPE(spec)==FORMAT_TYPE_SHORT){
				num = (short) va_arg(args, int);
			}else{
				num = va_arg(args, unsigned int);
			}
				str=number(str,num,spec&FORMAT_BASE_MASK,spec);
		}
	}
	if (size > 0) {
		if (str < end)
			*str = '\0';
		else
			end[-1] = '\0';
	}
	return str-buf;
}

(1)format_decode函数

作用:判断格式化的符号及类型;

flags:第0字节:若为数值,作为数值基数;
           第1字节:格式类型,字符,整形,长整型,短整型等;
           第2字节:若为数值,表示数值符号;

int format_decode(const char *fmt,unsigned int *flags){
	const char *start = fmt;

	*flags &= ~FORMAT_TYPE_MASK;
	*flags |= FORMAT_TYPE_NONE;
	for (; *fmt ; ++fmt) {
		if (*fmt == '%')
		break;
	}

	if (fmt != start || !*fmt)
		return fmt - start;

	do{
		fmt++;
		switch(*fmt){
			case 'l':
				SET_FORMAT_FLAG(*flags,FORMAT_FLAG_WIDTH);
				break;
			default:
				break;
		}
	}while(0);
	
	SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
	switch (*fmt) {
		case 'c':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_CHAR);
			break;

		case 's':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_STR);
			break;

		case 'o':
			SET_FORMAT_BASE(*flags,FORMAT_BASE_O);
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			break;

		case 'x':
		case 'X':
			SET_FORMAT_BASE(*flags,FORMAT_BASE_X);
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			break;

		case 'd':
		case 'i':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_INT);
			SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
			break;
		case 'u':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
			break;

	default:
		break;
	}
	return ++fmt-start;//参数偏移的字节数
}SET_FORMAT_FLAG(*flags,FORMAT_FLAG_WIDTH);
				break;
			default:
				break;
		}
	}while(0);
	
	SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
	switch (*fmt) {
		case 'c':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_CHAR);
			break;

		case 's':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_STR);
			break;

		case 'o':
			SET_FORMAT_BASE(*flags,FORMAT_BASE_O);
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			break;

		case 'x':
		case 'X':
			SET_FORMAT_BASE(*flags,FORMAT_BASE_X);
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			break;

		case 'd':
		case 'i':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_INT);
			SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
			break;
		case 'u':
			SET_FORMAT_TYPE(*flags,FORMAT_TYPE_UINT);
			SET_FORMAT_BASE(*flags,FORMAT_BASE_D);
			break;

	default:
		break;
	}
	return ++fmt-start;//参数偏移的字节数
}

 

 

 

(2)number函数

函数功能:根据类型进行数值转换

char *number(char *str, int num,int base,unsigned int flags){
	int i=0;
	int sign=0;

	if(FORMAT_SIGNED(flags)&&(signed int)num<0){
		sign=1;
		num=~num+1;
	}

	do{
		numbers[i++]=digits[do_div(num,base)];
	}while(num!=0);
	

	if(FORMAT_BASE(flags)==FORMAT_BASE_O){
		numbers[i++]='0';
	}else if(FORMAT_BASE(flags)==FORMAT_BASE_X){
		numbers[i++]='x';
		numbers[i++]='0';
	}else if(FORMAT_BASE(flags)==FORMAT_BASE_B){
		numbers[i++]='b';
		numbers[i++]='0';
	}
	if(sign)
		numbers[i++]='-';

	while (i-- > 0)
			*str++ = numbers[i];
	return str;
}

 

 

 

 

 

 

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 使用 `sprintf` 函数的前提是需要引入 `<stdio.h>` 头文件,该头文件中包含了该函数的声明。此外,还需要确保所使用的编译器中有该函数的实现。对于嵌入式系统中的使用,需要注意所使用的编译器是否支持 `printf` 和 `sprintf` 等函数,并且需要根据实际情况选择合适的格式化字符串和参数类型。另外,使用 `sprintf` 函数也需要注意内存的大小和安全性,避免造成缓冲区溢出等安全问题。 ### 回答2: 使用stm32 sprintf函数的前提是需要正确配置和初始化相关硬件和软件环境。 首先,需要正确配置和初始化微控制器的串行通信接口(USART,UART等),以便与外部设备(如计算机)进行通信。这包括设置波特率、数据位数、奇偶校验位、停止位等通信参数,以确保数据能够正确地传输。 其次,需要配置和初始化使用的GPIO引脚,将其配置为对应的串行通信功能。这涉及到设置引脚的工作模式(输入/输出)以及引脚的复用功能。 接下来,在使用sprintf函数之前,需要包含相关头文件,以便在代码中使用该函数。通常,需要包含<stdio.h>头文件来使用sprintf函数。 最后,在调用sprintf函数之前,需要准备好相关变量和字符串格式。sprintf函数的第一个参数是一个字符数组,用于存储格式化后的字符串。根据实际需要,可以使用不同的格式指示符(如%d,%s,%f等)来指定数据的格式和输出方式。其余参数是要格式化的数据,可以是整数、浮点数、字符串等类型。 以上是使用stm32 sprintf函数的前提。在正确配置和初始化硬件和软件环境后,即可使用该函数将数据格式化为字符串,以满足不同的输出需求。 ### 回答3: 使用stm32 sprintf函数的前提是需要先进行一些配置和准备工作。 首先,需要在STM32的开发环境中包含相应的头文件,这些头文件通常是通过包含"stdio.h"来实现的。"stdio.h"头文件包含了一系列输入输出的函数声明和相关常量的定义。 其次,需要配置适当的编译选项。在使用sprintf函数之前,需要确保编译器开启了对标准I/O库的支持。这可以通过在编译选项中包含"-u _printf_float"参数来实现。另外,还需要选择正确的浮点格式,以确保sprintf函数能够正确地处理浮点数。 最后,在使用sprintf函数之前,需要确保已经初始化了用于输出的串口或LCD等设备。sprintf函数通常通过调用与设备相关的底层函数来将输出发送到目标设备上。 总结来说,使用stm32 sprintf函数的前提是:包含相应的头文件、配置适当的编译选项、初始化输出设备。只有在这些准备工作完成之后,才可以安全地使用sprintf函数来进行字符串的格式化输出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值