0

printf組み込みシステム用の HAL を作成していますが、その一部として( というクラスを介して) 機能を再作成していますPrinter。組み込みシステムであるため、コードスペースが重要であり、デフォルトで浮動小数点のサポートを除外したいのprintfですが、HAL のユーザーがライブラリを再コンパイルすることなく、プロジェクトごとに浮動小数点を含めることができるようにします。 .

すべてのクラスのメソッド定義は、ヘッダー ファイルにインラインで含まれています。

printer.hのように見えます....

class Printer {
    public:
        Printer (const PrintCapable *printCapable)
             : m_printCapable(printCapable) {}

        void put_char (const char c) { ... }

#ifdef ENABLE_PRINT_FLOAT
        void put_float (const float f) { ... }
#endif

        void printf (const char fmt[], ...) {
            // Stuffs...

#ifdef ENABLE_PRINT_FLOAT
            // Handle floating point support
#endif
        }

    private:
        const PrintCapable *m_printCapable;
}

// Make it very easy for the user of this library to print by defining an instance for them
extern Printer out;

さて、これはうまくいくはずだと私は理解しています。

printer.cpp素晴らしくシンプルです:

#include <printer.h>
#include <uart/simplexuart.h>

const SimplexUART _g_simplexUart;
const Printer     out(&_g_simplexUart);

不必要なコードの肥大化: ライブラリをコンパイルし、ENABLE_PRINT_FLOAT定義せずにプロジェクトを作成すると、コード サイズは 9,216 kB になります。

必要なコードの肥大化: ライブラリとプロジェクトの両方を でコンパイルするとENABLE_PRINT_FLOAT、コード サイズは 9,348 kB になります。

必要なコード blo.... ああ、それは肥大化し ていませ 。しかし、いいえ...代わりに、7,092 kB のコード サイズと、正しく実行されないプログラムがあります。ENABLE_PRINT_FLOAT

最小サイズ: 両方をコンパイルせずにコンパイルするENABLE_PRINT_FLOATと、コード サイズはわずか 6,960 kB になります。

コードサイズを小さくし、クラスを柔軟にし、使いやすくするという目標を達成するにはどうすればよいですか?

ビルドシステムはCMakeです。完全なプロジェクト ソースはこちらです。

メインファイルは素晴らしくシンプルです:

#include <printer.h>

void main () {
    int i = 0;

    while (1) {
        out.printf("Hello world! %u %05.2f\n", i, i / 10.0);
        ++i;
        delay(250); // 1/4 second delay
    }
}
4

1 に答える 1

2

inline異なる翻訳単位で関数の定義が異なる場合、未定義の動作が発生します。printf()マクロの設定によって定義が変わるため、ENABLE_PRINT_FLOATこの効果が表示されます。

通常、コンパイラは、関数が複雑すぎると判断した場合、関数をインライン化しません。アウトオブラインの実装を作成し、リンク時にランダムなものを選択します。すべて同じなので、ランダムに選択しても問題ありません...ああ、それらは異なり、プログラムが壊れている可能性があります。

関数のテンプレート パラメーターを浮動小数点サポートにすることができprintf()ます。関数は次を使用して呼び出されます。

out.printf<false>("%d\n", i);
out.printf<true>("%f", f);

の実装はprintf()、適切な内部関数 (定義が同一である場合にコンパイラーがマージするようにする) に委譲し、false場合によっては浮動小数点サポートを無効にします: 何もしないか、失敗するか、またはアサートする可能性があります。

そもそも条件付きサポートを行わず、むしろストリームのようなインターフェースを使用する方が簡単かもしれません: さまざまなタイプのフォーマット関数が分離されているため、実際に使用されているものだけが選択されます。

ライブラリが C++11 を使用するオプションである場合は、可変個引数テンプレートを使用して状況に対処できます。個々のフォーマッタは、内部にディスパッチされる個別の関数として実装されますprintf()。この方法ではprintf()、処理する必要のある関数はありません。すべてのフォーマット。代わりに、必要な型フォーマッタのみが取り込まれます。実装は次のようになります。

inline char const* format(char const* fmt, int value) {
    // find format specifier and format value accordingly
    // then adjust fmt to point right after the processed format specifier
    return fmt;
}
inline char const* format(char const* fmt, double value) {
    // like the other but different
}
// othe formatters

inline int printf(char const* fmt) { return 0; }
template <typename A, typename... T>
inline int printf(char const* fmt, A&& arg, T&& args) {
    fmt = format(fmt, std::forward<A>(arg));
    return 1 + printf(fmt, std::forward<T>(args));
)

明らかに、異なるフォーマッタ間で共通のコードを除外する方法にはさまざまなアプローチがあります。ただし、全体的なアイデアは機能するはずです。理想的には、一般的なコードは、さまざまな用途の間ですべての重要なコードをコンパイラーにマージさせるために、可能な限り小さな作業を行います。良い副作用として、この実装は、フォーマット指定子が渡されるオブジェクトと一致していることを確認し、適切なエラーを生成するか、何らかの方法でフォーマットを適切に処理することができます。

于 2014-12-01T04:19:27.883 に答える