通過串列埠實現printf和scanf函式

2021-07-16 14:58:46 字數 3244 閱讀 9008

**

草根老師部落格(程姚根)

在做裸板開發時,常常需要通過輸出或者通過串列埠輸入一些資訊。

在有作業系統機器上,我們很少關心輸入和輸出的問題。因為有很多現成的庫函式供我們呼叫。在做裸板開發時,可沒有現成庫函式供我們呼叫,一切都需要我們自己實現。

下面我們通過串列埠在裸板上實現乙個printf和scanf函式。

printf主要用來進行格式化輸出,scanf函式主要用來進行格式化輸入的。這裡個函式都是不定引數函式,這裡簡單介紹一下不定參函式實現方法。

一、不定引數的造型

function(type  arg,...);

一般第乙個引數arg指定引數的個數,

int function(3,arg1,arg2,arg3);

這是一種顯示的告訴編譯器有幾個引數,第乙個引數3,即代表後面有三個引數。

但也不是唯一的,比如printf的第乙個引數是乙個字串,它是通過這個引數來確定有幾個引數的.

int printf(const char *format, ...);

例如我們呼叫的時候

printf("%d,%s,%d",i,str,j);

第乙個引數是"%d,%s,%d",通過分析它我們可以知道有幾個。

二、函式的引數壓棧

1. 固定的引數函式呼叫過程

一般函式引數入棧是按照從右向左的順序入棧。這樣第乙個引數arg1就放在了棧頂的位置。

2.變參函式呼叫過程

通過上面的引數入棧方式我們可以得到如下結論:

如果想將棧中的引數讀出來,我們只需要知道,棧頂元素的位址即第乙個引數的位址即可。通過前面變參函式的分析,通過變參函式第乙個引數可以知道傳遞的引數個數。根據引數入棧的順序,我們可以知道第乙個引數是放在棧頂位置的。

總結一下,現在我們已經獲得兩個條件了:

1.棧中引數的個數

2.棧頂元素的位址

有了這兩個條件,我們就可以從棧中讀取我們想要的引數了。

當然,每個引數都有自己的型別,還有的就是位元組對齊了。在讀取引數的時候,這些問題都必須考慮到。

幸運的是這些問題在已經被大牛們解決了,已經封裝成相應的巨集,我們在操作的時候只需要知道這些巨集的含義即可。

三、變參函式常用巨集

typedef   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)

這些巨集在不同的作業系統,有不同的實現,想使用的話,只需要包含標頭檔案stdarg.h就可以了。

(1)va_start巨集的作用 : 

v是第乙個引數,通過前面我們知道,第乙個引數就是用來表明有幾個引數,它不是我們實際需要的引數。我們通過它來計算出,第乙個實際引數的位址,主意哦是實際引數,可不是第乙個表明引數個數的引數位址,讓ap指標變數儲存。

(1)va_arg巨集的作用:

通過va_start,我們的ap的指標已經指向了第乙個實際引數。

可以看到的是ap指標先更新了,然後又減了乙個值,最終把這個值返回。這裡面的t代表即將獲得引數的型別。

可以看出,通過va_arg巨集我們獲得每個實際引數的值。

(2)va_end巨集的作用

將ap指標賦值為null,即0

下面我們自己寫乙個測試程式來看一下,這些巨集怎麼使用。

#include

<

stdio.h

>

#include

<

stdlib.h

>

#include

<

stdarg.h

>

intmy_printf

(char 

*fmt,.

..)}

else

}//將ap賦值為null

va_end(ap

);return 0;}

intmain

(int

argc

,const char 

*argv

) 實際上,格式化的轉換有現成的函式可以呼叫,例如:vsprintf()和vsscanf()這些函式的源**可以從bootloader和核心原始碼上獲得。

四、常用格式轉換函式

int vsprintf(char *str, const char *format, va_list ap);

這個函式的功能,就是把輸入的格式字串進行解釋,把解釋好的字串放在str。這個函式的原始碼可以直接在核心中獲得。

int vsscanf(const char *str, const char *format, va_list ap);

str中是我們從鍵盤上輸入的一些字串,format是我們呼叫scanf的時候輸入的格式串。通過這些資訊,vsscanf函式解發布每個變數應該賦為什麼值。這個函式的原始碼可以直接在核心中獲得。

有了這兩個函式後,我們就可以通過串列埠封裝自己的printf和scanf了。

(1)通過串列埠實現printf函式

intprintf

(const

char 

*fmt,.

..)return n;}

(2)通過串列埠實現scanf函式

intscanf

(const char 

*fmt,.

..)else

}va_start

(args

,fmt);

i =vsscanf

(buffer

,fmt

,args);

va_end

(args);

send_char

('r');

send_char

('n');

return i;}

關於scanf使用的一點說明,參考:

通過串列埠實現printf和scanf函式

在做裸板開發時,常常需要通過輸出或者通過串列埠輸入一些資訊。在有作業系統機器上,我們很少關心輸入和輸出的問題。因為有很多現成的庫函式供我們呼叫。在做裸板開發時,可沒有現成庫函式供我們呼叫,一切都需要我們自己實現。下面我們通過串列埠在裸板上實現乙個printf和scanf函式。printf主要用來進行...

使用串列埠實現接收和傳送功能

一 前言 本文以串列埠 usart1為例,初步實現接收和傳送功能,使用的是stm32f1板子,基於火哥教學做的自我小結。電腦裝置需要安裝串列埠除錯助手和usb轉串列埠ch340g的驅動。二 正文 1.硬體設計 將 ch340g 的 txd 引腳與 usart1 的 rx 引腳連線,ch340g 的 ...

串列埠實現FIFO接受資料

基本原理 靜態佇列 串列埠的fifo簡單讀取實現 功能,實現串列埠的fifo實現 使用方法 版本 v1.0.0 include sys.h include usartbuf.h usartype usart fifo read usart recerivepoint rusart,uint8 t b...