nanoprintf 是針對嵌入式系統的 snprintf 和 vsnprintf 的無阻礙實現,完全啟用後,其目標是符合 C11 標準。主要的例外是浮點、科學記數法( %e
、 %g
、 %a
)以及需要wcrtomb
存在的轉換。根據 N2630,可選擇支援 C23 二進位整數輸出。 snprintf 和 vsnprintf 的安全擴充可以選擇配置為在緩衝區溢位事件時傳回修剪或全空的字串。
此外,nanoprintf 可用於解析 printf 樣式的格式字串以提取各種參數和轉換說明符,而無需執行任何實際的文字格式化。
nanoprintf 不進行記憶體分配並且使用少於 100 位元組的堆疊。它在 Cortex-M0 架構上編譯為約 740-2640 位元組的目標程式碼,具體取決於配置。
所有程式碼均以C99 的最小方言編寫,以實現最大的編譯器相容性,在clang + gcc + msvc 上以最高警告級別進行乾淨地編譯,不會引發UBsan 或Asan 的任何問題,並在32 位元和64 位元架構上進行了詳盡的測試。 nanoprintf 確實包含 C 標準標頭,但僅將它們用於 C99 類型和參數清單;除了編譯器可能發出的任何內部大整數算術呼叫之外,不會對 stdlib / libc 進行任何呼叫。像往常一樣,如果您要為 msvc 進行本機編譯,則需要一些特定於 Windows 的標頭。
nanoprintf 是 stb 函式庫風格的單一頭檔。存儲庫的其餘部分是測試和腳手架,不需要使用。
nanoprintf 是靜態可配置的,因此使用者可以在大小、編譯器要求和功能集之間找到平衡。浮點轉換、「大」長度修飾符和大小寫回都是可配置的,並且僅在明確請求時才進行編譯,有關詳細信息,請參閱配置。
將以下程式碼新增至原始檔案之一以編譯 nanoprintf 實作:
// define your nanoprintf configuration macros here (see "Configuration" below) #define NANOPRINTF_IMPLEMENTATION #include "path/to/nanoprintf.h"
然後,在任何要使用 nanoprintf 的檔案中,只需包含標頭並呼叫 npf_ 函數:
#include "nanoprintf.h" void print_to_uart(void) { npf_pprintf(&my_uart_putc, NULL, "Hello %s%c %d %u %fn", "worl", 'd', 1, 2, 3.f); } void print_to_buf(void *buf, unsigned len) { npf_snprintf(buf, len, "Hello %s", "world"); }
有關更多詳細信息,請參閱“直接使用 nanoprintf”和“Wrap nanoprintf”範例。
我想要一個單檔案公共網域嵌入式 printf,在最小配置(引導程式等)下大小小於 1KB,啟用浮點功能後大小小於 3KB。
在韌體工作中,我通常希望 stdio 的字串格式化沒有系統呼叫或檔案描述符層要求;在您想要登入小型緩衝區或直接發送到總線的小型系統中,它們幾乎從不需要。此外,許多嵌入式 stdio 實作比它們需要的更大或更慢——這對於引導程式工作很重要。如果您不需要任何系統呼叫或 stdio 鈴聲+口哨,您可以簡單地使用 nanoprintf 和nosys.specs
並精簡您的建置。
該程式碼針對大小而不是可讀性或結構進行了最佳化。不幸的是,模組化和「清潔性」(無論這意味著什麼)會在如此小的規模下增加開銷,因此大多數功能和邏輯都被推入npf_vpprintf
中。這不是正常嵌入式系統程式碼應有的樣子;這是#ifdef
湯,很難理解,如果你必須在實現中深入研究,我深表歉意。希望各種測試能夠提供您參考。
或者,也許你是比我更好的程式設計師!在這種情況下,請幫助我使程式碼更小、更簡潔,而不會使足跡更大,或將我推向正確的方向。 :)
nanoprintf有4個主要功能:
npf_snprintf
:像 snprintf 一樣使用。
npf_vsnprintf
:像 vsnprintf 一樣使用( va_list
支援)。
npf_pprintf
:像 printf 一樣使用每個字元的寫入回呼(半主機、UART 等)。
npf_vpprintf
:與npf_pprintf
類似,但採用va_list
。
pprintf
變體採用一個回呼來接收要列印的字元和使用者提供的上下文指標。
將NULL
或nullptr
傳遞給npf_[v]snprintf
不寫入任何內容,並且僅傳回格式化字串的長度。
nanoprintf本身不提供printf
或putchar
;這些被視為系統級服務,而 nanoprintf 是一個實用程式庫。不過,nanoprintf 希望是滾動您自己的printf
的一個很好的構建塊。
nanoprintf 函數都會傳回相同的值:傳送到回呼(對於 npf_pprintf)的字元數或寫入提供足夠空間的緩衝區的字元數。空終止符 0 位元組不是計數的一部分。
C 標準允許 printf 函數在無法執行字串或字元編碼或輸出流遇到 EOF 的情況下傳回負值。由於 nanoprintf 不關心檔案等作業系統資源,且不支援wchar_t
支援的l
長度修飾符,因此任何執行時間錯誤要么是內部錯誤(請報告!),要么是不正確的使用。因此,nanoprintf 僅傳回表示格式化字串包含多少位元組的非負值(再次減去空終止符位元組)。
nanoprintf 具有以下靜態配置標誌。
NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS
:設定為0
或1
。啟用欄位寬度說明符。
NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS
:設定為0
或1
。啟用精確度說明符。
NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS
:設定為0
或1
。啟用浮點說明符。
NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS
:設定為0
或1
。啟用超大修飾符。
NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS
:設定為0
或1
。啟用二進位說明符。
NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS
:設定為0
或1
。啟用%n
進行回寫。
NANOPRINTF_VISIBILITY_STATIC
:可選定義。將原型標記為沙盒 nanoprintf static
。
如果未指定配置標誌,nanoprintf 將預設為「合理」嵌入值以嘗試提供協助:啟用浮點數,但停用回寫、二進位和大型格式化程式。如果明確指定任何配置標誌,nanoprintf 要求明確指定所有標誌。
如果使用停用的格式說明符功能,則不會發生轉換,而只會列印格式說明符字串。
nanoprintf 具有以下浮點特定配置定義。
NANOPRINTF_CONVERSION_BUFFER_SIZE
:可選,預設為23
。設定用於儲存轉換值的字元緩衝區的大小。設定為較大的數字可以列印包含更多字元的浮點數。緩衝區大小包含整數部分、小數部分和小數分隔符,但不包含符號和填滿字元。如果該數字不適合緩衝區,則會列印err
。請注意大尺寸,因為轉換緩衝區是在堆疊記憶體上分配的。
NANOPRINTF_CONVERSION_FLOAT_TYPE
:可選,預設為unsigned int
。設定浮點轉換演算法使用的整數類型,它決定轉換的精確度。可以設定為任何無符號整數類型,例如uint64_t
或uint8_t
。
預設情況下,npf_snprintf 和 npf_vsnprintf 的行為符合 C 標準:提供的緩衝區將被填滿但不會溢位。如果字串超出緩衝區,則會將空終止符位元組寫入緩衝區的最後一個位元組。如果緩衝區為null
或大小為零,則不會寫入任何位元組。
如果您定義NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW
並且您的字串大於緩衝區,則緩衝區的第一個位元組將被空終止符位元組覆蓋。這在本質上與微軟的 snprintf_s 類似。
在所有情況下,如果有足夠的空間,nanoprintf 將傳回將寫入緩衝區的位元組數。根據 C 標準,該值不考慮空終止符位元組。
nanoprintf 僅使用堆疊內存,不使用並發原語,因此在內部它不關心其執行環境。這使得同時從多個執行上下文調用,或用另一個npf_
調用(例如,ISR 或其他)中斷npf_
調用是安全的。如果您將npf_pprintf
與相同的npf_putc
目標同時使用,則需要您確保回呼內的正確性。如果您從多個執行緒npf_snprintf
到同一個緩衝區,則會出現明顯的資料競賽。
與printf
一樣, nanoprintf
需要以下形式的轉換規範字串:
[flags][field width][.precision][length modifier][conversion specifier]
旗幟
不存在或存在以下多項情況:
0
:用前導零字元填滿欄位。
-
:將欄位中的轉換結果左對齊。
+
:有符號轉換總是以+
或-
字元開頭。
:(空格) 如果第一個轉換的字元不是符號,則插入空格字元。
#
:寫入額外字元( 0x
表示十六進位, .
表示空浮點數, '0' 表示空八進位等)。
欄位寬度(如果啟用)
指定轉換的總欄位寬度的數字,並新增填充。如果欄位寬度為*
,則從下一個可變參數讀取欄位寬度。
精度(如果啟用)
前綴為.
,一個指定數字或字串精度的數字。如果精度為*
,則從下一個可變參數讀取精度。
長度修飾符
不存在或存在以下多項情況:
h
:使用整數和回寫可變參數寬度的short
。
L
:使用long double
作為 float vararg 寬度(注意:然後它將被向下轉換為double
)
l
:使用long
、 double
或 Wide 可變參數寬度。
hh
:使用char
來表示整數和回寫可變參數寬度。
ll
: (large 說明符) 使用long long
作為整數和回寫可變參數寬度。
j
:(大說明符)使用[u]intmax_t
類型作為整數和回寫可變參數寬度。
z
:(大說明符)使用size_t
類型作為整數和回寫可變參數寬度。
t
:(大說明符)使用ptrdiff_t
類型作為整數和回寫可變參數寬度。
轉換說明符
正是以下其中一項:
%
:百分號文字
c
:字符
s
:以空字元結尾的字串
i
/ d
:有符號整數
u
:無符號整數
o
:無符號八進制整數
x
/ X
:無符號十六進位整數
p
:指針
n
:寫入寫入指標 vararg 的位元組數
f
/ F
: 浮點小數
e
/ E
:浮點科學(未實現,列印浮點十進位)
g
/ G
:最短浮點(未實現,列印浮點十進位)
a
/ A
:浮點十六進位(未實現,列印浮點十進位)
b
/ B
: 二進位整數
浮點轉換是透過將數字的整數和小數部分提取到兩個單獨的整數變數中來執行的。對於每個部分,透過迭代地將尾數適當地乘以和除以 2 和 5,將指數從以 2 為底縮放到以 10 為底。動態選擇縮放操作的順序(取決於值)以保留盡可能多的尾數的最高有效位元。該值距離小數點分隔符號越遠,縮放累積的誤差就越大。透過平均N
位的轉換整數類型寬度,此演算法保留N - log2(5)
或N - 2.322
位元的精度。此外,小數分隔符號後最多2 ^^ N - 1
的整數部分和最多N - 2.322
位的小數部分可以完美轉換,不會丟失任何位。
由於 float ->fixed 程式碼對原始浮點值位元進行操作,因此不執行任何浮點操作。這使得 nanoprintf 能夠在 Cortex-M0 等軟浮點架構上有效地格式化浮點,無論是否經過「快速數學」等最佳化,其功能都相同,並最大限度地減少程式碼佔用量。
%e
/ %E
、 %a
/ %A
和%g
/ %G
說明符會被解析,但不會被格式化。若使用,輸出將與使用%f
/ %F
時相同。歡迎拉請求! :)
不存在寬字元支援: %lc
和%ls
欄位要求將 arg 轉換為字元數組,就像呼叫 wcrtomb 一樣。當涉及語言環境和字元集轉換時,很難保留“nano”這個名稱。因此, %lc
和%ls
行為分別類似%c
和%s
。
目前唯一支援的浮點轉換是小數形式: %f
和%F
。歡迎拉請求!
CI 建置設定為使用 gcc 和 nm 來測量每個拉取請求的編譯大小。請參閱最近執行的「提交前檢查」「大小報告」作業輸出。
以下尺寸測量是針對 Cortex-M0 構建進行的。
Configuration "Minimal": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - arm-none-eabi-nm --print-size --size-sort npf.o 00000046 00000002 t npf_bufputc_nop 00000048 00000010 t npf_putc_cnt 00000032 00000014 t npf_bufputc 00000270 00000016 T npf_pprintf 000002cc 00000016 T npf_snprintf 00000000 00000032 t npf_utoa_rev 00000286 00000046 T npf_vsnprintf 00000058 00000218 T npf_vpprintf Total size: 0x2e2 (738) bytes Configuration "Binary": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - arm-none-eabi-nm --print-size --size-sort npf.o 00000046 00000002 t npf_bufputc_nop 00000048 00000010 t npf_putc_cnt 00000032 00000014 t npf_bufputc 000002a8 00000016 T npf_pprintf 00000304 00000016 T npf_snprintf 00000000 00000032 t npf_utoa_rev 000002be 00000046 T npf_vsnprintf 00000058 00000250 T npf_vpprintf Total size: 0x31a (794) bytes Configuration "Field Width + Precision": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - arm-none-eabi-nm --print-size --size-sort npf.o 00000046 00000002 t npf_bufputc_nop 00000048 00000010 t npf_putc_cnt 00000032 00000014 t npf_bufputc 000004fe 00000016 T npf_pprintf 0000055c 00000016 T npf_snprintf 00000000 00000032 t npf_utoa_rev 00000514 00000048 T npf_vsnprintf 00000058 000004a6 T npf_vpprintf Total size: 0x572 (1394) bytes Configuration "Field Width + Precision + Binary": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - arm-none-eabi-nm --print-size --size-sort npf.o 00000046 00000002 t npf_bufputc_nop 00000048 00000010 t npf_putc_cnt 00000032 00000014 t npf_bufputc 00000560 00000016 T npf_pprintf 000005bc 00000016 T npf_snprintf 00000000 00000032 t npf_utoa_rev 00000576 00000046 T npf_vsnprintf 00000058 00000508 T npf_vpprintf Total size: 0x5d2 (1490) bytes Configuration "Float": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - arm-none-eabi-nm --print-size --size-sort npf.o 00000046 00000002 t npf_bufputc_nop 00000048 00000010 t npf_putc_cnt 00000032 00000014 t npf_bufputc 00000618 00000016 T npf_pprintf 00000674 00000016 T npf_snprintf 00000000 00000032 t npf_utoa_rev 0000062e 00000046 T npf_vsnprintf 00000058 000005c0 T npf_vpprintf Total size: 0x68a (1674) bytes Configuration "Everything": arm-none-eabi-gcc -c -x c -Os -I/__w/nanoprintf/nanoprintf -o npf.o -mcpu=cortex-m0 -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 - arm-none-eabi-nm --print-size --size-sort npf.o 0000005a 00000002 t npf_bufputc_nop 0000005c 00000010 t npf_putc_cnt 00000046 00000014 t npf_bufputc 000009da 00000016 T npf_pprintf 00000a38 00000016 T npf_snprintf 00000000 00000046 t npf_utoa_rev 000009f0 00000048 T npf_vsnprintf 0000006c 0000096e T npf_vpprintf Total size: 0xa4e (2638) bytes
取得環境並執行測試:
克隆或分叉此儲存庫。
從根目錄運行./b
(對於 Windows 用戶,從根目錄運行py -3 build.py
)
這將為您的主機環境建立所有單元、一致性和編譯測試。任何測試失敗都將傳回非零退出程式碼。
nanoprintf開發環境使用cmake和ninja。如果您的路徑中有這些, ./b
將使用它們。如果沒有, ./b
將下載它們並將其部署到path/to/your/nanoprintf/external
。
nanoprintf 使用 GitHub Actions 進行所有持續整合建置。 GitHub Linux 建置使用我的 Docker 儲存庫中的這個 Docker 映像。
此矩陣建構 [調試、發布] x [32 位元、64 位元] x [Mac、Windows、Linux] x [gcc、clang、msvc],減去 32 位元 clang Mac 配置。
其中一個測試套件是 printf 測試套件的分支,該套件已獲得 MIT 許可。它作為許可目的的子模組存在 - nanoprintf 是公共領域,因此這個特定的測試套件是可選的,默認情況下被排除。要建構它,請透過更新子模組來檢索它,並將--paland
標誌新增到您的./b
呼叫中。根本不需要使用 nanoprintf。
浮點到整數轉換的基本思想受到 Wojciech Muła 的 float -> 64:64 固定演算法的啟發,並透過 Oskars Rubenis 添加動態縮放和可配置整數寬度進一步擴展。
我將 printf 測試套件移植到 nanoprintf。它最初來自 mpaland printf 專案程式碼庫,但被 Eyal Rozenberg 等人採用和改進。 (Nanoprintf 有很多自己的測試,但這些也非常徹底且非常好!)
二進位實現基於 Jörg Wunsch 的 N2630 提案指定的要求,希望能被 C23 接受!