hantas's blog

ブログ移転しました→ http://blog.taniho.net/

myprintf -> myiostream

サークル内での話なのですが,マイコン開発をする際のバグ取りに絶大な威力を発揮する方法として "myprintf" が代々伝わっています。 (いつからなのでしょうか?)

"myprintf" - マイプリントエフ [名]
1. SCI1を利用してTeraTermに文字を送るため,SH向けに作成されたヘッダファイルのこと
2. 1.のヘッダファイルを用いて文字を送ること
3. (広義) 1.のヘッダファイルをRXなど他のマイコンに対応させたファイルのこと

……正直なところ,こんなイメージが部内に漂っているような気がします。 代々伝わってきた "myprintf" がデバッグに広く使われているのは嬉しい限りですが,少し悲しくも感じます。

問題点

まあそんなことは良いのですが,本題はこのヘッダファイルについてです。

int myprintf(const char*, ...); は,通常のprintfと同様にシリアルで文字列を送信できるようにした関数ですが, これにはいくつかの問題があって,

  • バッファサイズが固定(100byte)なので100文字以上になる文字列を渡すと範囲外アクセスをする危険性がある
  • 内部的にvsprintfを用いているのでバッファオーバーフローの危険性がある
  • 条件付き書式の修飾子を書き間違えやすい(一部のコンパイラではエラーを出さないので,人為的ミスが多発しやすい)

などなど,あまりよろしいものではありません。

書き換える

というわけで,この"myprintf"を捨て去るため,C++的に作り変えることにしました。

ところで,入門者向けのコードでこのようなものを目にすることが多いと思います。

std::cout << "Hello World!" << std::endl;

この構文が不思議で不思議でたまりませんでした。 シェルのリダイレクトっぽい雰囲気があるので言わんとすることはわかるのですが,Cしか触ったことのなかった自分にとってはとても謎。

実はこれ,ビットシフト演算子をオーバーライドしているのですね。 そして返り値にstd::coutの参照を与えることで,1度に複数の文字列を出力させているようです。

そこで,myprintfを一新し,このcout風に書けるように自前のクラスを作りたいと思います。

いくつかのサイトを参考にしながら"ComPc"クラスにオレオレcoutを実装することにしました。

  • ComPcクラスはシングルトン
  • 左ビットシフト演算子をオーバーライドして,std::string,const char*, 整数型を右辺に取ったときに文字(もしくは10進数表記)を出力
  • CpmPc::hex(整数型) を右辺に取ったときは16進の文字を出力

書いてて意味が分からない気がしますが,次のように使います。

uint8_t hoge = 0x3C;
ComPc *compc = ComPc::getInstance();
*compc << "Hello World!\n";
*compc << "value: " << compc->hex(hoge) << "\n";

// output
Hello World!
value: 3C

実装コードの一部分を掲載しておきますが,作りかけのため参考程度としてください。

(send1byte, sendnbyteは別に定義しているメンバ関数で,1または整数バイトをUSARTを通して送信する関数です)

unsigned char ComPc::bit2hex(const uint8_t val){
    if(val < 0x0A) return '0' + val;
    else if(val > 0x0F) return 'X';
    else return 'A' + (val - 0x0A);
}

std::string ComPc::hex(const uint8_t val){
    std::string tmp;
    tmp += " 0x";
    for (int i=1; i>=0; i--) {
        tmp += bit2hex((val>>(4*i)) & 0x0F);
    }
    tmp += " ";
    return tmp;
}

ComPc& ComPc::operator << (const char chr) {
    send1byte(chr);
    return *this;
}

ComPc& ComPc::operator << (const std::string& str) {
    sendnbyte(str.c_str(), str.length());
    return *this;
}

実際にはそれぞれの関数をオーバーロードして,様々な引数を取れるようにしています。

ポイントは,ComPcの参照を返しているところです。

まとめ

  • コードがC++っぽくなってきた
  • 本家とは使い方が異なるので,そのうち変更したい

参考

C 言語よりお得な C++ その8 | RVF/RC45 blog

C++ - シングルトンのベターな実装方法 - Qiita