RX マイコンでC++

少し間が空いたけど、またまたRXマイコン関連ですー

日本の組み込み(趣味)マイコンでは、まだ「C」が主流で、「C++」の情報が少ないように感じる。
デバイスドライバーもCで書かれたものが殆どで、C++で書かれた物が少ない(日本国内では観た事が無い)
※たとえば、複数チャネルを備えたUARTなど、テンプレートクラスを使えば、拡張性のあるコードがシンプルに書けると思う。

Windows などのアプリケーションプログラマーは別だが、組み込み系プログラマーは、まだC++に対して、「負」の感情を持っているのだろうか?

最近AVRでプログラムする機会があり、C++が実用レベルで扱える事を知ったー
又、arduino を触る機会があり、そのコードはC++が中心である事も知った。
ARM系、海外の組み込みプログラムでは、C++が普通に使われている、何故日本は未だにCなのだろうか?

さて、RXマイコンの開発環境なのだがー、C++でプログラムする場合に必要な事を考えてみたい。

AVRではメモリーの制限からSTLを扱う事が困難だ、RXマイコンなら全てのデバイスでは無理だけど、少なくともRX621など、RAMもROMもある程度豊富にあればSTLも怖くないと思われる。
以前にSH2AでSTLが正しく動作しなかったのだが、それは、静的な初期化関数のコンストラクターが正しく呼ばれていない事に起因するようだった。

ARMの gcc などの情報から、「.ctors」セクションに初期化が必要な関数のコンストラクターテーブルが格納される事は判ったので、リンカースクリプトに.ctorsセクションのロード命令を記述して、簡単なコードを書き、アセンブルリストを観て確認してみた。

typedef unsigned int uint32;

class func {

uint32 count_;

public:
func() : count_(0) { }

void service() { ++count_; }
uint32 get() const { return count_; }
void set(uint32 v) { count_ = v; }
};

func func_;

↑たとえば、こんなクラスを宣言して、静的なクラスを配置する、「func_」は、main 関数が実行される以前にコンストラクターが呼ばれなければならない。
※funcクラスでは、コンストラクターで、count_を0で初期化している。

リンカースクリプトに、以下のように書いたが、mapファイルを観ても、.ctorsのサイズは0となっている・・・
.ctors :
{
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}

最適化を0にして、アセンブラリストを確認すると、初期化関数自体はあるが、当然呼ばれていない・・・

fff80b31 <__GLOBAL__sub_I_func_>:
fff80b31: 7e a6 push.l r6
fff80b33: ef 06 mov.l r0, r6
fff80b35: 66 11 mov.l #1, r1
fff80b37: fb 2e ff ff 00 mov.l #0xffff, r2
fff80b3c: 39 c7 ff bsr.w fff80b03 <__Z41__static_initialization_and_destruction_0ii>
fff80b3f: 3f 66 01 rtsd #4, r6-r6

そこで、C++の標準的リンカースクリプトを参考にしてみたー「/usr/tkdn-20110720/rx-elf/rx-elf/lib/rx.ld」
そして、判ったー、最近の gcc では、別のセクションに格納されているようだー、これは、4.6 くらいからそうなったようだー

PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = .);

※ちなみに簡単に説明すると、「.preinit_array、.init_array」セクションが、コンストラクターの開始アドレステーブルが格納されるセクションであり、「.fini_array」はデストラクターの開始テーブルが格納されるセクションとなっているようだー
又、「KEEP」は、重要なキーワードで、最適化された場合、どこからも参照されないアドレステーブルは、削除される為、それが起こらないようにするものだ。

「__GLOBAL__sub_I_func_」は、「__Z41__static_initialization_and_destruction_0ii」を呼んでいるのだが、コンストラクターとデストラクターが共通になっており、引数を使って呼び分けているようだ。

初期化コードはこんな感じ~

int main(int argc, char**argv);

extern int sbss;
extern int ebss;
extern int idata;
extern int sdata;
extern int edata;

extern int _preinit_array_start;
extern int _preinit_array_end;
extern int _init_array_start;
extern int _init_array_end;

int init(void)
{
// R/W-data セクションのコピー
{
int *src = &idata;
int *dst = &sdata;
while(dst < &edata) { *dst++ = *src++; } } // bss セクションのクリア { int *dst = &sbss; while(dst < &ebss) { *dst++ = 0; } } // 静的コンストラクターの実行(C++ ) { int *p = &_preinit_array_start; while(p < &_preinit_array_end) { void (*prog)(void) = (void *)*p++; (*prog)(); } } { int *p = &_init_array_start; while(p < &_init_array_end) { void (*prog)(void) = (void *)*p++; (*prog)(); } } // main の起動 int argc = 0; char **argv = 0; int ret = main(argc, argv); return ret; } C++ テスト・コード

※今後 rx.ld を解析して、自分のシステムに合った物に修正する必要がある、リンカースクリプトにはまだまだ不明な事が多い・・・

とりあえず今回はここまで。