nanoprintf は組み込みシステム向けの snprintf および vsnprintf の制約のない実装であり、完全に有効にすると C11 標準への準拠を目指します。主な例外は、浮動小数点、科学表記法 ( %e
、 %g
、 %a
)、およびwcrtomb
存在を必要とする変換です。 C23 バイナリ整数出力は、N2630 に従ってオプションでサポートされます。 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 は静的に構成できるため、ユーザーはサイズ、コンパイラ要件、機能セットの間のバランスを見つけることができます。浮動小数点変換、「大きな」長さ修飾子、およびサイズのライトバックはすべて構成可能であり、明示的に要求された場合にのみコンパイルされます。詳細については、「構成」を参照してください。
次のコードをソース ファイルの 1 つに追加して、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 を直接使用する」および「nanoprintf をラップする」例を参照してください。
私は、最小構成 (ブートローダーなど) で 1 KB 未満、浮動小数点の付属機能を有効にすると 3 KB 未満になる、単一ファイルのパブリック ドメイン ドロップイン printf を望んでいました。
ファームウェアの作業では、通常、syscall またはファイル記述子層の要件なしで stdio の文字列フォーマットを必要とします。小さなバッファにログインしたり、バスに直接出力したりする小さなシステムでは、これらはほとんど必要ありません。また、多くの組み込み標準入出力実装は、必要以上に大きいか遅いため、これはブートローダーの動作にとって重要です。 syscall や標準出力の付属機能が必要ない場合は、nanoprintf とnosys.specs
使用するだけでビルドをスリム化できます。
このコードは、読みやすさや構造ではなく、サイズについて最適化されています。残念ながら、モジュール性と「クリーンさ」(それが何を意味するにせよ) により、この小規模なスケールではオーバーヘッドが増加するため、ほとんどの機能とロジックがnpf_vpprintf
にプッシュされます。これは、通常の組み込みシステムのコードがどうあるべきかではありません。これは#ifdef
スープであり、理解するのが難しいため、実装で詳しく調べなければならない場合は申し訳ありません。さまざまなテストが、ハックする際のガイドレールとして機能することを願っています。
あるいは、あなたは私よりもはるかに優れたプログラマーである可能性があります。その場合は、フットプリントを大きくせずにこのコードをより小さく、よりクリーンにするか、正しい方向に私を促してください。 :)
nanoprintf には 4 つの主な機能があります。
npf_snprintf
: snprintf と同様に使用します。
npf_vsnprintf
: vsnprintf と同様に使用します ( va_list
サポート)。
npf_pprintf
: 文字ごとの書き込みコールバック (セミホスティング、UART など) を備えた printf と同様に使用します。
npf_vpprintf
: npf_pprintf
と同様に使用しますが、 va_list
受け取ります。
pprintf
バリエーションは、印刷する文字とユーザー指定のコンテキスト ポインターを受け取るコールバックを受け取ります。
NULL
またはnullptr
npf_[v]snprintf
に渡すと、何も書き込まれず、フォーマットされた文字列の長さのみが返されます。
nanoprintf はprintf
やputchar
自体を提供しません。これらはシステムレベルのサービスとみなされ、nanoprintf はユーティリティ ライブラリです。ただし、 nanoprintf は、独自のprintf
展開するための優れた構成要素であることが期待されます。
nanoprintf 関数はすべて同じ値を返します。つまり、コールバック (npf_pprintf の場合) に送信された文字数、または十分なスペースを提供するバッファーに書き込まれた文字数です。ヌル終端文字の 0 バイトはカウントの一部ではありません。
C 標準では、文字列または文字エンコーディングを実行できない場合、または出力ストリームが EOF に遭遇した場合に、printf 関数が負の値を返すことが許可されています。 nanoprintf はファイルなどの OS リソースを意識せず、 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
またはサイズ 0 の場合、バイトは書き込まれません。
NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW
を定義し、文字列がバッファーより大きい場合、バッファーの最初のバイトはヌル終端バイトで上書きされます。これは、Microsoft の snprintf_s と精神的に似ています。
すべての場合において、nanoprintf は、十分なスペースがあった場合にバッファーに書き込まれるバイト数を返します。 C 標準に従って、この値にはヌル終端バイトは考慮されません。
nanoprintf はスタック メモリのみを使用し、同時実行プリミティブを使用しないため、内部的には実行環境を意識しません。これにより、複数の実行コンテキストから同時に呼び出したり、 npf_
呼び出しを別のnpf_
呼び出し (ISR など) で中断したりすることが安全になります。 npf_pprintf
同じnpf_putc
ターゲットと同時に使用する場合、コールバック内の正確性を確認するのはユーザーの責任です。複数のスレッドから同じバッファに対してnpf_snprintf
実行すると、明らかなデータ競合が発生します。
printf
と同様に、 nanoprintf
次の形式の変換指定文字列を想定します。
[flags][field width][.precision][length modifier][conversion specifier]
フラグ
以下のいずれか、またはそれ以上:
0
: フィールドの先頭にゼロ文字を埋め込みます。
-
: フィールド内の変換結果を左詰めにします。
+
: 符号付き変換は常に+
または-
文字で始まります。
: (スペース) 最初に変換された文字が記号でない場合は、スペース文字が挿入されます。
#
: 追加の文字を書き込みます (16 進数の場合は0x
、空の浮動小数点数の場合は.
、空の 8 進数の場合は '0' など)。
フィールド幅(有効な場合)
変換の合計フィールド幅を指定する数値で、パディングが追加されます。フィールド幅が*
の場合、フィールド幅は次の可変引数から読み取られます。
精度(有効な場合)
接頭辞が.
、数値または文字列の精度を指定する数値。精度が*
の場合、精度は次の可変長引数から読み取られます。
長さ修飾子
以下のいずれか、またはそれ以上:
h
: 整数およびライトバック可変引数幅にはshort
を使用します。
L
: float 可変長引数の幅にlong double
使用します (注: その後、 double
にキャストされます)
l
: long
、 double
、または Wide 可変長引数幅を使用します。
hh
: 整数およびライトバック可変引数幅にはchar
使用します。
ll
: (大きい指定子) 整数およびライトバック可変長引数の幅には、 long long
使用します。
j
: (大きい指定子) 整数およびライトバック可変引数幅には[u]intmax_t
タイプを使用します。
z
: (大きい指定子) 整数およびライトバック可変引数幅にはsize_t
タイプを使用します。
t
: (大きい指定子) 整数およびライトバック可変引数幅にはptrdiff_t
タイプを使用します。
変換指定子
まさに次のいずれかです。
%
: パーセント記号リテラル
c
:キャラクター
s
: Null で終了する文字列
i
/ d
: 符号付き整数
u
: 符号なし整数
o
: 符号なし 8 進整数
x
/ X
: 符号なし 16 進整数
p
: ポインタ
n
: ポインタ可変長引数に書き込まれるバイト数を書き込みます。
f
/ F
: 浮動小数点10進数
e
/ E
: 科学浮動小数点 (未実装、浮動小数点を表示)
g
/ G
: 浮動小数点の最短 (未実装、浮動小数点を表示)
a
/ A
: 16 進浮動小数点 (未実装、浮動小数点 10 進数を表示)
b
/ B
: 2 進整数
浮動小数点変換は、数値の整数部分と小数部分を 2 つの別個の整数変数に抽出することによって実行されます。次に、各部分について、仮数に 2 と 5 を適切に乗算および除算することを繰り返すことにより、指数が 2 を基数から 10 を基数にスケーリングします。スケーリング操作の順序は、仮数の最上位ビットをできるだけ保持するために (値に応じて) 動的に選択されます。値が小数点の区切り記号から離れるほど、スケーリングで累積される誤差が大きくなります。平均N
ビットの変換整数型幅では、アルゴリズムはN - log2(5)
またはN - 2.322
ビットの精度を維持します。さらに、 2 ^^ N - 1
までの整数部分と、小数点以下のN - 2.322
ビットまでの小数部分は、ビットを失うことなく完全に変換されます。
float -> fix コードは生の浮動小数点値ビットを操作するため、浮動小数点演算は実行されません。これにより、nanoprintf は、Cortex-M0 のようなソフトフロート アーキテクチャ上でフロートを効率的にフォーマットし、「高速演算」などの最適化の有無にかかわらず同様に機能し、コード フットプリントを最小限に抑えることができます。
%e
/ %E
、 %a
/ %A
、および%g
/ %G
指定子は解析されますが、フォーマットされません。使用した場合、出力は%f
/ %F
使用した場合と同じになります。プルリクエストは大歓迎です! :)
ワイド文字のサポートは存在しません。 %lc
フィールドと%ls
フィールドでは、wcrtomb を呼び出したかのように arg を char 配列に変換する必要があります。ロケールと文字セットの変換が関係すると、「nano」という名前を維持するのは困難になります。したがって、 %lc
と%ls
それぞれ%c
と%s
のように動作します。
現在サポートされている浮動小数点数変換は、10 進形式%f
および%F
のみです。プルリクエストは大歓迎です!
CI ビルドは、gcc と nm を使用してすべてのプル リクエストのコンパイルされたサイズを測定するように設定されています。最近の実行については、Presubmit Checks の「サイズ レポート」ジョブの出力を参照してください。
次のサイズ測定は、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 構成を差し引いて構築します。
1 つのテスト スイートは、MIT ライセンスを取得した printf テスト スイートからのフォークです。これはライセンス目的のサブモジュールとして存在します。nanoprintf はパブリック ドメインであるため、この特定のテスト スイートはオプションであり、デフォルトでは除外されます。これを構築するには、サブモジュールを更新して取得し、 ./b
b 呼び出しに--paland
フラグを追加します。 nanoprintf を使用する必要はまったくありません。
float から int への変換の基本的なアイデアは、Wojciech Muła の float -> 64:64 固定アルゴリズムに触発され、Oskars Rubenis によって動的スケーリングと構成可能な整数幅を追加することでさらに拡張されました。
printf テスト スイートを nanoprintf に移植しました。これはもともと mpaland printf プロジェクトのコードベースからのものですが、Eyal Rozenberg らによって採用され、改良されました。 (Nanoprintf には独自のテストが多数ありますが、これらも非常に徹底的で非常に優れています!)
バイナリ実装は、Jörg Wunsch の N2630 提案で指定された要件に基づいており、C23 に受け入れられることが期待されています。