最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C語言嵌入式實(shí)現(xiàn)支持浮點(diǎn)輸出的printf示例詳解

 更新時(shí)間:2023年01月30日 11:04:07   作者:MacRsh  
這篇文章主要為大家介紹了C語言嵌入式實(shí)現(xiàn)支持浮點(diǎn)輸出的printf示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡介

mr-printf 模塊為 mr-library 項(xiàng)目下的可裁剪模塊,以C語言編寫,可快速移植到各種平臺(tái)(主要以嵌入式mcu為主)。

mr-printf 模塊用以替代 libc 中 printf, 可在較小資源占用的同時(shí)支持絕大部分 printf 功能,于此同時(shí)還支持對單獨(dú)功能模塊的裁剪以減少用戶不需要功能的資源占用。

背景

printf 大家應(yīng)該使用的比較多,但是在嵌入式平臺(tái)中,尤其是單片機(jī)中,libc中的printf對內(nèi)存的占用較高,尤其是加上浮點(diǎn)輸出功能時(shí),占用更是能翻倍。同時(shí)移植適配相對困難,不同編譯器下,需要適配的接口不同,遇到問題也因?yàn)榭床坏皆创a,無從下手。

故有了寫自己的printf想法。現(xiàn)在網(wǎng)上也有不少自己寫printf的教程,但是在我實(shí)際按照教程編寫時(shí)又遇到了許多問題很多教程并不能正確實(shí)現(xiàn)功能,所以我把寫完的代碼開源出來,同時(shí)分享下我在編寫時(shí)遇到的問題。

C語言可變參數(shù)函數(shù)

C 語言允許定義參數(shù)數(shù)量可變的函數(shù),這稱為可變參數(shù)函數(shù)。這種函數(shù)需要固定數(shù)量的強(qiáng)制參數(shù),后面是數(shù)量可變的可選參數(shù)。 如 mr_printf(char *fmt, ...) 前面的 fmt為 char 類型參數(shù),是固定數(shù)量的強(qiáng)制參數(shù),后面的 ... 為數(shù)量可變的可選參數(shù)。

同時(shí)我們要了解函數(shù)參數(shù)的入棧順序,例如我們調(diào)用了mr_printf("%d,%f",a,b); 那么首先 "%d,%f"就是fmt這個(gè)char*,這個(gè)是確定的,然后就是兩個(gè)參數(shù) a b ,加入我們采用的從左往右入棧,也就是fmt 先入棧然后a b,這就會(huì)導(dǎo)致,你拿到了棧指針,但是因?yàn)椴恢?code>a b的類型,所以定位不到a 也就是首個(gè)參數(shù)的地址。

但是我們反過來,采用從右往左入棧,那么我們將會(huì)得到fmt的地址,然后只需要對fmt的地址 + fmt的大小,就能得到a 的地址。實(shí)現(xiàn)此功能的函數(shù)也叫va_start名字也很形象,是一切的開始。然后我們通過分析 fmt中的信息,就能通過對 a的地址 + a的大小得到b的地址,這一步驟也叫va_arg。

最后當(dāng)我們處理完所有的信息后我們需要把棧指針歸零防止出現(xiàn)野指針,也就是va_end。好了我們已經(jīng)學(xué)會(huì)了可變參數(shù)函數(shù)的開始和結(jié)束,那么我們就可以開始應(yīng)用了。

踩坑

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的實(shí)現(xiàn)時(shí)候都看到過這段代碼,確實(shí)這是沒有問題的,這是x86上的代碼,我們可以通過學(xué)習(xí)這個(gè)代碼的思路來了解整個(gè)可變參數(shù)函數(shù)的實(shí)現(xiàn)過程。但當(dāng)你把這段代碼移植到你的stm32等設(shè)備上時(shí)你就會(huì)發(fā)現(xiàn),同樣的代碼在電腦上跑沒有問題,但是單片機(jī)上卻不行,就是應(yīng)為這段的問題,在gcc環(huán)境下應(yīng)該是下面這樣,并不能通過上面的函數(shù)直接去操作棧指針,當(dāng)然最好的辦法其實(shí)是引入#include <stdarg.h>這個(gè)頭文件,其中包含了對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)

功能實(shí)現(xiàn)

首先我們需要定義一個(gè)函數(shù)將字符輸出到我們的硬件MR_WEAK void mr_putc(char data),MR_WEAK為宏定義,不同平臺(tái)可能關(guān)鍵字不同,將 void mr_putc(char data)定義為一個(gè)弱函數(shù),該函數(shù)主要功能為將data字符輸出到例如串口等設(shè)備。

同時(shí)我們定義int mr_printf(char *fmt, ...) 函數(shù),參入?yún)?shù)為一個(gè) char *和不定數(shù)量的可變參數(shù)。然后定義一個(gè) va_list ap 用來獲取可變參數(shù)。

我們先初始化ap指針,方法剛剛已經(jīng)講過,即對fmt參數(shù)偏移sizeof(fmt),調(diào)用va_start(ap,fmt)即可。

接下來我們就要開始分析fmt中的信息了,我們需要處理的只有 %x命令,其他的通過我們自定義的輸出函數(shù)直接輸出即可。因?yàn)樽址慕Y(jié)尾都是\0,所以我們就能寫出以下代碼:

int mr_printf(char *fmt, ...)
{
	va_list ap;
	char putc_buf[20]; //輸出緩沖區(qū),減少運(yùn)算加速輸出
	 unsigned int u_val;
	 int val, bits, flag;
	 double f_val;
	 char *str;
	 int res = 0;
	/* move ap to fmt + sizeof(fmt) */
  	va_start(ap,fmt);
	while(*fmt != '\0')
	{
		if(*fmt == '%')
		{
			++ fmt;
			"處理函數(shù)"
		}
		else
		{
			mr_putc(*fmt);
	        ++ res;
	        ++ fmt;
		}
	}
	/* set ap = null */
    va_end(ap);
    return res;
}

接下來我們就需要編寫中間的處理函數(shù)了,我們暫且需要支持 %d,%x,%o,%u,%s,%c,%f 這幾個(gè)指令 我們先開一個(gè)switch

switch (*fmt)
{
}

然后先處理最簡單的 %d

/* mr_printf signed int to DEC */
case 'd':
	/* get value */
    val = va_arg(ap,int);
    if(val < 0)						//判斷正負(fù)
    {
      val = - val;
      mr_putc('-');
      ++ res;
    }
    /* get value bits */
    bits = 0;
    while(val)
    {
      putc_buf[bits] = '0' + val % 10;		//獲取整型位數(shù)的同時(shí),將每一位按低位到高位存入緩沖區(qū)
      val /= 10;
      ++ bits;
    }
    res += bits;
    /* put value bits */
    while (bits)
    {
      -- bits;
      mr_putc(putc_buf[bits]);			//將每一位從高到低從緩沖區(qū)中輸出
    }
    ++ fmt;
    continue;

同理處理下 %u

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

與此同時(shí) %x%o也是同樣的道理僅需修改取余和除的值即可,直接貼代碼

 /* mr_printf unsigned int to HEX */
case 'x':
	 /* get value */
	 u_val = va_arg(ap,unsigned int);
	 /* get value bits */
	 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;
	 /* put value bits */
	 while (bits)
	 {
	   -- bits;
	   mr_putc(putc_buf[bits]);
	 }
	 ++ fmt;
	 continue;
/* mr_printf unsigned int to OCT */
case 'o':
	 /* get value */
	 u_val = va_arg(ap,unsigned int);
	 /* get value bits */
	 bits = 0;
	 while(u_val)
	 {
	   putc_buf[bits] = '0' + u_val % 8;
	   u_val /= 8;
	   ++ bits;
	 }
	 res += bits;
	 /* put value bits */
	 while (bits)
	 {
	   -- bits;
	   mr_putc(putc_buf[bits]);
	 }
	 ++ fmt;
	 continue;

%s%c就更簡單了

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

最難的其實(shí)是對float的輸出,當(dāng)你用上面的思路一位一位取出數(shù)據(jù)的同時(shí),就會(huì)發(fā)現(xiàn),每做一個(gè)浮點(diǎn)運(yùn)算,就會(huì)引入新的誤差,所以要盡可能少的做浮點(diǎn)運(yùn)算,同時(shí)因?yàn)檫€需支持%.2f這種指令需要在switch前面加上下面一段代碼記錄需要輸出多少位。

/* dispose %.x */
if(*fmt == '.')
{
  ++ fmt;
  flag = (int)(*fmt - '0');
  ++ fmt;
}
else
{
  flag = 187; // N(46) + U(53) + L(44) + L(44) = NULL(187)
}
/* mr_printf float */
case 'f':
	/* get value */
	f_val = va_arg(ap,double);
	if(f_val < 0)
	{
	  f_val = - f_val;						//判斷正負(fù)
	  mr_putc('-');
	  ++ res;
	}
	/* separation int and float */
	val = (int)f_val;					// 分離整數(shù)和小數(shù),整數(shù)將按上面處理整數(shù)的部分輸出,小數(shù)部分單獨(dú)處理
	f_val -= (double)val;
	/* get int value bits */
	bits = 0;
	if(val == 0)
	{
	  mr_putc('0');
	  ++ res;
	}
	while (val)
	{
	  putc_buf[bits] = '0' + val % 10;
	  val /= 10;
	  ++ bits;
	}
	res += bits;
	/* put int value bits */
	while (bits)
	{
	  --bits;
	  mr_putc(putc_buf[bits]);
	}
	/* dispose float */
	if(flag != 0)
	{
	  mr_putc('.');
	  ++ res;
	}
	if(flag > 6)													//判斷需要輸出幾位小數(shù)
	  flag = 6;
	val = (int)((f_val * 1000000.0f) + 0.5f);		//僅做一次浮點(diǎn)運(yùn)算,同時(shí)對誤差進(jìn)行處理忽略最低幾位小數(shù)引入的誤差
	/* get float value bits */
	bits = 0;
	while (bits < 6)
	{
	  putc_buf[bits] = '0' + val % 10;					//使用輸出整數(shù)的方法將小數(shù)輸出出去
	  val /= 10;
	  ++ bits;
	}
	res += flag;
	/* put int value bits */
	while (flag)
	{
	  --flag;
	  -- bits;
	  mr_putc(putc_buf[bits]);
	}
	++ fmt;
	continue;

好了通過上面的講解你應(yīng)該已經(jīng)會(huì)寫printf了,或者下載開源代碼使用。

開源代碼

倉庫鏈接 gitee.com/chen-fanyi/…

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

以上就是C語言嵌入式實(shí)現(xiàn)支持浮點(diǎn)輸出的printf示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C語言嵌入式浮點(diǎn)輸出printf的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言淺析函數(shù)的用法

    C語言淺析函數(shù)的用法

    C語言函數(shù)是用來模塊化構(gòu)建程序的。如果你的功能少,你可以全都寫在mian函數(shù)中,但是當(dāng)實(shí)現(xiàn)功能多的時(shí)候,如果全寫在main的函數(shù)里,不僅代碼不美觀,而且函數(shù)實(shí)現(xiàn)的時(shí)候結(jié)構(gòu)復(fù)雜,代碼重復(fù)
    2022-07-07
  • 超詳細(xì)VScode調(diào)試教程tasks.json和launch.json的設(shè)置

    超詳細(xì)VScode調(diào)試教程tasks.json和launch.json的設(shè)置

    vscode是一個(gè)輕量級(jí)的文本編輯器,但是它的擴(kuò)展插件可以讓他拓展成功能齊全的IDE,這其中就靠的是tasks.json和launch.json的配置,下面這篇文章主要給大家介紹了關(guān)于超詳細(xì)VScode調(diào)試教程tasks.json和launch.json設(shè)置的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • C語言實(shí)現(xiàn)三子棋游戲(初級(jí)版)

    C語言實(shí)現(xiàn)三子棋游戲(初級(jí)版)

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)三子棋游戲初級(jí)版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • C++中圖片重命名實(shí)現(xiàn)代碼

    C++中圖片重命名實(shí)現(xiàn)代碼

    這篇文章主要介紹了C++中圖片重命名實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2017-05-05
  • C++利用Socket實(shí)現(xiàn)主機(jī)間的UDP/TCP通信

    C++利用Socket實(shí)現(xiàn)主機(jī)間的UDP/TCP通信

    這篇文章主要為大家詳細(xì)介紹了C++如何利用Socket實(shí)現(xiàn)主機(jī)間的UDP/TCP通信功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-01-01
  • 一文掌握 C++ 智能指針的使用方法

    一文掌握 C++ 智能指針的使用方法

    C++11 引入了智能指針的概念,使用了引用計(jì)數(shù)的想法,讓程序員不再需要關(guān)心手動(dòng)釋放內(nèi)存。關(guān)于C++指針得內(nèi)容下面文章將為大家做一個(gè)詳細(xì)介紹
    2021-09-09
  • 詳解C語言內(nèi)核中的鏈表與結(jié)構(gòu)體

    詳解C語言內(nèi)核中的鏈表與結(jié)構(gòu)體

    Windows內(nèi)核中是無法使用vector容器等數(shù)據(jù)結(jié)構(gòu)的,當(dāng)我們需要保存一個(gè)結(jié)構(gòu)體數(shù)組時(shí),就需要使用內(nèi)核中提供的專用鏈表結(jié)構(gòu)。本文分享了幾個(gè)內(nèi)核中使用鏈表存儲(chǔ)多個(gè)結(jié)構(gòu)體的通用案例,希望對你有所幫助
    2022-09-09
  • C語言實(shí)現(xiàn)二叉樹的示例詳解

    C語言實(shí)現(xiàn)二叉樹的示例詳解

    這篇文章主要為大家詳細(xì)介紹了C語言中二叉樹的算法實(shí)現(xiàn)以及二叉樹的遍歷算法與應(yīng)用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-06-06
  • C++實(shí)現(xiàn)LeetCode(66.加一運(yùn)算)

    C++實(shí)現(xiàn)LeetCode(66.加一運(yùn)算)

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(66.加一運(yùn)算),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • c++先序二叉樹的構(gòu)建詳解

    c++先序二叉樹的構(gòu)建詳解

    在本篇文章里小編給大家分享了關(guān)于c++先序二叉樹的構(gòu)建的相關(guān)知識(shí)點(diǎn),需要的朋友們跟著學(xué)習(xí)下。
    2019-04-04

最新評論

开原市| 长汀县| 中超| 南阳市| 阿鲁科尔沁旗| 大名县| 宁河县| 沧源| 靖西县| 新安县| 和龙市| 秀山| 昭平县| 宜城市| 临泽县| 台北县| 青田县| 济阳县| 潢川县| 汉寿县| 商水县| 鄂伦春自治旗| 东港市| 苏尼特左旗| 鲁甸县| 广东省| 崇左市| 长海县| 措勤县| 噶尔县| 长兴县| 虎林市| 峨山| 长丰县| 乌鲁木齐县| 冀州市| 萝北县| 彭泽县| 南安市| 克山县| 罗平县|