返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言嵌入式实现支持浮点输出的printf示例详解
  • 515
分享到

C语言嵌入式实现支持浮点输出的printf示例详解

C语言嵌入式浮点输出printfC语言嵌入式printf 2023-01-30 12:01:17 515人浏览 安东尼
摘要

目录简介背景C语言可变参数函数踩坑功能实现简介 mr-printf 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。

简介

mr-printf 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。

mr-printf 模块用以替代 libc 中 printf, 可在较小资源占用的同时支持绝大部分 printf 功能,于此同时还支持对单独功能模块的裁剪以减少用户不需要功能的资源占用。

背景

printf 大家应该使用的比较多,但是在嵌入式平台中,尤其是单片机中,libc中的printf对内存的占用较高,尤其是加上浮点输出功能时,占用更是能翻倍。同时移植适配相对困难,不同编译器下,需要适配的接口不同,遇到问题也因为看不到源代码,无从下手。

故有了写自己的printf想法。现在网上也有不少自己写printf的教程,但是在我实际按照教程编写时又遇到了许多问题很多教程并不能正确实现功能,所以我把写完的代码开源出来,同时分享下我在编写时遇到的问题。

C语言可变参数函数

C 语言允许定义参数数量可变的函数,这称为可变参数函数。这种函数需要固定数量的强制参数,后面是数量可变的可选参数。 如 mr_printf(char *fmt, ...) 前面的 fmt为 char 类型参数,是固定数量的强制参数,后面的 ... 为数量可变的可选参数。

同时我们要了解函数参数的入栈顺序,例如我们调用了mr_printf("%d,%f",a,b); 那么首先 "%d,%f"就是fmt这个char*,这个是确定的,然后就是两个参数 a b ,加入我们采用的从左往右入栈,也就是fmt 先入栈然后a b,这就会导致,你拿到了栈指针,但是因为不知道a b的类型,所以定位不到a 也就是首个参数的地址。

但是我们反过来,采用从右往左入栈,那么我们将会得到fmt的地址,然后只需要对fmt的地址 + fmt的大小,就能得到a 的地址。实现此功能的函数也叫va_start名字也很形象,是一切的开始。然后我们通过分析 fmt中的信息,就能通过对 a的地址 + a的大小得到b的地址,这一步骤也叫va_arg。

最后当我们处理完所有的信息后我们需要把栈指针归零防止出现野指针,也就是va_end。好了我们已经学会了可变参数函数的开始和结束,那么我们就可以开始应用了。

踩坑

typedef   char  * va_list;                  //将char*别名为va_list;
#define   _INTSIZEOF(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))  
#define   va_start(ap,v)   (ap = (va_list)&v + _INTSIZEOF(v))
#define   va_arg(ap,t)     (*(t*)((ap += _INTSIZEOF(t)) -  _INTSIZEOF(t)))
#define   va_end(ap)      (ap = (va_list)0)

相信很多人在搜printf的实现时候都看到过这段代码,确实这是没有问题的,这是x86上的代码,我们可以通过学习这个代码的思路来了解整个可变参数函数的实现过程。但当你把这段代码移植到你的STM32等设备上时你就会发现,同样的代码在电脑上跑没有问题,但是单片机上却不行,就是应为这段的问题,在GCc环境下应该是下面这样,并不能通过上面的函数直接去操作栈指针,当然最好的办法其实是引入#include <stdarg.h>这个头文件,其中包含了对va_list va_start va_end va_arg 的定义。

typedef __builtin_va_list       __gnuc_va_list;
typedef __gnuc_va_list          va_list;
#define va_start(v,l)           __builtin_va_start(v,l)
#define va_end(v)               __builtin_va_end(v)
#define va_arg(v,l)             __builtin_va_arg(v,l)

功能实现

首先我们需要定义一个函数将字符输出到我们的硬件MR_WEAK void mr_putc(char data)MR_WEAK为宏定义,不同平台可能关键字不同,将 void mr_putc(char data)定义为一个弱函数,该函数主要功能为将data字符输出到例如串口等设备。

同时我们定义int mr_printf(char *fmt, ...) 函数,参入参数为一个 char *和不定数量的可变参数。然后定义一个 va_list ap 用来获取可变参数。

我们先初始化ap指针,方法刚刚已经讲过,即对fmt参数偏移sizeof(fmt),调用va_start(ap,fmt)即可。

接下来我们就要开始分析fmt中的信息了,我们需要处理的只有 %x命令,其他的通过我们自定义的输出函数直接输出即可。因为字符串的结尾都是\0,所以我们就能写出以下代码:

int mr_printf(char *fmt, ...)
{
	va_list ap;
	char putc_buf[20]; //输出缓冲区,减少运算加速输出
	 unsigned int u_val;
	 int val, bits, flag;
	 double f_val;
	 char *str;
	 int res = 0;
	
  	va_start(ap,fmt);
	while(*fmt != '\0')
	{
		if(*fmt == '%')
		{
			++ fmt;
			"处理函数"
		}
		else
		{
			mr_putc(*fmt);
	        ++ res;
	        ++ fmt;
		}
	}
	
    va_end(ap);
    return res;
}

接下来我们就需要编写中间的处理函数了,我们暂且需要支持 %d,%x,%o,%u,%s,%c,%f 这几个指令 我们先开一个switch

switch (*fmt)
{
}

然后先处理最简单的 %d


case 'd':
	
    val = va_arg(ap,int);
    if(val < 0)						//判断正负
    {
      val = - val;
      mr_putc('-');
      ++ res;
    }
    
    bits = 0;
    while(val)
    {
      putc_buf[bits] = '0' + val % 10;		//获取整型位数的同时,将每一位按低位到高位存入缓冲区
      val /= 10;
      ++ bits;
    }
    res += bits;
    
    while (bits)
    {
      -- bits;
      mr_putc(putc_buf[bits]);			//将每一位从高到低从缓冲区中输出
    }
    ++ fmt;
    continue;

同理处理下 %u


case 'u':
	
	u_val = va_arg(ap,unsigned int);
	
	bits = 0;
	while(u_val)
	{
	  putc_buf[bits] = '0' + u_val % 10;
	  u_val /= 10;
	  ++ bits;
	}
	res += bits;
	
	while (bits)
	{
	  -- bits;
	  mr_putc(putc_buf[bits]);
	}
	++ fmt;
	continue;

与此同时 %x%o也是同样的道理仅需修改取余和除的值即可,直接贴代码

 
case 'x':
	 
	 u_val = va_arg(ap,unsigned int);
	 
	 bits = 0;
	 while(u_val)
	 {
	   putc_buf[bits] = '0' + u_val % 16;
	   if(putc_buf[bits] > '9')
	     putc_buf[bits] = 'A' + (putc_buf[bits] - '9' - 1);
	   u_val /= 16;
	   ++ bits;
	 }
	 res += bits;
	 
	 while (bits)
	 {
	   -- bits;
	   mr_putc(putc_buf[bits]);
	 }
	 ++ fmt;
	 continue;

case 'o':
	 
	 u_val = va_arg(ap,unsigned int);
	 
	 bits = 0;
	 while(u_val)
	 {
	   putc_buf[bits] = '0' + u_val % 8;
	   u_val /= 8;
	   ++ bits;
	 }
	 res += bits;
	 
	 while (bits)
	 {
	   -- bits;
	   mr_putc(putc_buf[bits]);
	 }
	 ++ fmt;
	 continue;

%s%c就更简单了


case 's':
	str = va_arg(ap,char *);
	while (*str != '\0')
	{
	  mr_putc(*str);
	  ++ str;
	  ++ res;
	}
	++ fmt;
	continue;

case 'c':
	mr_putc(va_arg(ap,int));
	++ res;
	++ fmt;
	continue;

最难的其实是对float的输出,当你用上面的思路一位一位取出数据的同时,就会发现,每做一个浮点运算,就会引入新的误差,所以要尽可能少的做浮点运算,同时因为还需支持%.2f这种指令需要在switch前面加上下面一段代码记录需要输出多少位。


if(*fmt == '.')
{
  ++ fmt;
  flag = (int)(*fmt - '0');
  ++ fmt;
}
else
{
  flag = 187; // N(46) + U(53) + L(44) + L(44) = NULL(187)
}

case 'f':
	
	f_val = va_arg(ap,double);
	if(f_val < 0)
	{
	  f_val = - f_val;						//判断正负
	  mr_putc('-');
	  ++ res;
	}
	
	val = (int)f_val;					// 分离整数和小数,整数将按上面处理整数的部分输出,小数部分单独处理
	f_val -= (double)val;
	
	bits = 0;
	if(val == 0)
	{
	  mr_putc('0');
	  ++ res;
	}
	while (val)
	{
	  putc_buf[bits] = '0' + val % 10;
	  val /= 10;
	  ++ bits;
	}
	res += bits;
	
	while (bits)
	{
	  --bits;
	  mr_putc(putc_buf[bits]);
	}
	
	if(flag != 0)
	{
	  mr_putc('.');
	  ++ res;
	}
	if(flag > 6)													//判断需要输出几位小数
	  flag = 6;
	val = (int)((f_val * 1000000.0f) + 0.5f);		//仅做一次浮点运算,同时对误差进行处理忽略最低几位小数引入的误差
	
	bits = 0;
	while (bits < 6)
	{
	  putc_buf[bits] = '0' + val % 10;					//使用输出整数的方法将小数输出出去
	  val /= 10;
	  ++ bits;
	}
	res += flag;
	
	while (flag)
	{
	  --flag;
	  -- bits;
	  mr_putc(putc_buf[bits]);
	}
	++ fmt;
	continue;

好了通过上面的讲解你应该已经会写printf了,或者下载开源代码使用。

开源代码

仓库链接 gitee.com/chen-fanyi/…

路径:master/mr-library/ device / mr_printf

以上就是C语言嵌入式实现支持浮点输出的printf示例详解的详细内容,更多关于C语言嵌入式浮点输出printf的资料请关注编程网其它相关文章!

--结束END--

本文标题: C语言嵌入式实现支持浮点输出的printf示例详解

本文链接: https://lsjlt.com/news/178768.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C语言嵌入式实现支持浮点输出的printf示例详解
    目录简介背景C语言可变参数函数踩坑功能实现简介 mr-printf 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。...
    99+
    2023-01-30
    C语言嵌入式浮点输出printf C语言嵌入式printf
  • C语言详解格式控制符scanf与printf的输入输出
    目录一、使用scanf输入和printf输出1、scanf的使用2、printf的使用二、常用输出格式1、%md2、%0md3、%.mf三、使用getchar输入putchar输出单...
    99+
    2024-04-02
  • Go语言实现彩色输出示例详解
    目录简介说明支持Linux彩色输出支持Windows彩色输出Golang IDE输出是不支持的使用CODE DEMO小结简介 在逛github时发现一个好玩的Go项目,彩色输出文本 ...
    99+
    2024-04-02
  • C语言输出孪生素数的实现示例
    目录1.题目内容:2.输入格式:3输出格式:1.题目内容: 孪生素数是指间隔为 2 的相邻素数,例如最小的孪生素数对是3和5,5和7也是(5虽重复但算作2组)。 2.输入格式: 输入...
    99+
    2024-04-02
  • C语言实现字符串转浮点函数的示例
      字符串不仅可以转换为整数,也可以转换为浮点数,字符串转浮点数函数原型如下: float __cdecl __mingw_strtof (const ...
    99+
    2024-04-02
  • C语言实现栈的示例详解
    目录前言一. 什么是栈二. 使用什么来实现栈三. 栈的实现3.1 头文件3.2 函数实现3.3 完整代码四. 栈的用处前言 前一段时间,我们试着用C语言实现了数据结构中的顺序表,单链...
    99+
    2024-04-02
  • C语言实现输入ascii码,输出对应的字符方式
    目录C语言输入ascii码,输出对应的字符C语言ASCII码与转义字符与代码间关系基础知识回顾总结C语言输入ascii码,输出对应的字符 源代码 #include<stdio...
    99+
    2023-01-28
    C语言输入ascii码 输出字符 输入ascii码
  • C语言进阶输入输出重定向与fopen函数使用示例详解
    目录正片开始输入输出重定向fopen函数正片开始 大多数情况下,我们所熟知的输入输出都是标准I/O(标准输入输出),也就是我们在写代码时会直接从键盘读取,从屏幕输出。但是当我们涉及到...
    99+
    2024-04-02
  • C#实现日期时间的格式化输出的示例详解
    目录单个字母格式化自定义格式化DateTime被放在System命名空间下,在顶级语句中不必using可直接使。 单个字母格式化 DateTime最常用的属性是Now,表示当前的时刻...
    99+
    2023-03-13
    C#实现日期时间格式化输出 C#时间格式化输出 C#时间格式化
  • C语言输出孪生素数的实现示例分析
    今天就跟大家聊聊有关C语言输出孪生素数的实现示例分析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1.题目内容:孪生素数是指间隔为 2 的相邻素数,例如最小的孪生素数对是3和5,5和...
    99+
    2023-06-26
  • C语言实现阶乘的示例详解
    目录前言1.阶乘实现1.1理论步骤1.2实践结果2.连续乘层相加实现2.1理论步骤2.2实践结果前言 在现实中,我们做数学题总会遇到阶乘问题,这在计算机中也不例外。 那我们应该怎么实...
    99+
    2024-04-02
  • C语言实现队列的示例详解
    目录前言一. 什么是队列二. 使用什么来实现栈三. 队列的实现3.1头文件3.2 函数的实现四.完整代码前言 前一段时间,我们试着用C语言实现了数据结构中的顺序表,单链表,双向循环链...
    99+
    2024-04-02
  • C语言模拟实现密码输入的示例代码
    目录引言思路分析代码实现代码分析引言 登录账号时我们要输入密码,密码输入错误时会提示密码错误。有时密码的输入次数会被限制,例如银行卡,当我们3次密码都输入错误时卡会被冻结。下面用C语...
    99+
    2024-04-02
  • C语言详细图解浮点型数据的存储实现
    目录在引入知识之前,先来看一个案例,就知道了解浮点型数据存储的重要性与必要性。 举个例子: #define _CRT_SECURE_NO_WARNINGS 1 #include<...
    99+
    2024-04-02
  • C语言实现单元测试的示例详解
    目录前沿使用前提测试框架如下测试方法编写文件验证前沿 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际...
    99+
    2024-04-02
  • C语言实现冒泡排序算法的示例详解
    目录1. 问题描述2. 问题分析3. 算法设计动图演示4. 程序设计设计一设计二结论5. 流程框架6. 代码实现7. 问题拓展1. 问题描述 对N个整数(数据由键盘输入)进行升序排列...
    99+
    2024-04-02
  • C语言实现生成新春福字的示例详解
    目录主要代码字面量以及数据结构定义一个回调函数,刷新福字应用初始化程序主程序效果展示快新年了,支付宝扫福活动又开始了,每次都要百度找福,这次不想找了,自己写一个程序生成各种字体的福字...
    99+
    2024-04-02
  • C语言线性表的链式表示及实现详解
    目录前言代码实现1. 单链表的结点构造2. 构造一个空的头结点3. 对线性表进行赋值4.对线性表进行销毁5.对线性表进行重置6.判断线性表是否为空7.获取线性表的长度8.获取线性表某...
    99+
    2024-04-02
  • C语言实现对文件进行操作的示例详解
    目录前言文件指针文件的打开和关闭文件的打开方式文件读写函数二进制方式存储文件对文件进行拷贝文件随机读写函数文件缓冲区前言 文件操作 在运行程序的时候,此时数据是存放在内存中,当程序退...
    99+
    2023-05-15
    C语言实现文件操作 C语言文件操作 C语言文件
  • C语言实现顺序表的基本操作的示例详解
    目录一、认识顺序表1.线性表2.顺序表的概念及结构二、顺序表的基本操作(接口实现)1.初始化顺序表2.打印顺序表3.尾插4.尾删5.扩容6.头插7.头删8.任意位置插入9.任意位置删...
    99+
    2022-11-13
    C语言顺序表基本操作 C语言顺序表操作 C语言顺序表
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作